Пример #1
0
def track_completed_order(sender, order=None, **kwargs):  # pylint: disable=unused-argument
    """Emit a tracking event when an order is placed."""
    if not (is_segment_configured() and order.total_excl_tax > 0):
        return

    user_tracking_id, lms_client_id = parse_tracking_context(order.user)

    analytics.track(
        user_tracking_id,
        'Completed Order',
        {
            'orderId': order.number,
            'total': str(order.total_excl_tax),
            'currency': order.currency,
            'products': [
                {
                    # For backwards-compatibility with older events the `sku` field is (ab)used to
                    # store the product's `certificate_type`, while the `id` field holds the product's
                    # SKU. Marketing is aware that this approach will not scale once we start selling
                    # products other than courses, and will need to change in the future.
                    'id': line.partner_sku,
                    'sku': mode_for_seat(line.product),
                    'name': line.product.course.id,
                    'price': str(line.line_price_excl_tax),
                    'quantity': line.quantity,
                    'category': line.product.get_product_class().name,
                } for line in order.lines.all()
            ],
        },
        context={
            'Google Analytics': {
                'clientId': lms_client_id
            }
        },
    )
Пример #2
0
    def test_flush_with_product(self):
        """
        Verify the method fires 'Product Removed' Segment event with the correct information when basket is not empty
        """
        self.site1.siteconfiguration = SiteConfigurationFactory()
        self.site1.siteconfiguration.segment_key = 'fake_key'
        basket = create_basket(empty=True)
        basket.owner = factories.UserFactory()
        basket.site = self.site1
        basket.save()

        course = CourseFactory()
        seat = course.create_or_update_seat('verified', True, 100, self.partner)
        basket.add_product(seat)

        properties = translate_basket_line_for_segment(basket.lines.first())
        user_tracking_id, lms_client_id, lms_ip = parse_tracking_context(basket.owner)
        context = {
            'ip': lms_ip,
            'Google Analytics': {
                'clientId': lms_client_id
            }
        }

        with mock.patch.object(Client, 'track') as mock_track:
            basket.flush()
            mock_track.assert_called_once_with(user_tracking_id, 'Product Removed', properties, context=context)
Пример #3
0
def track_completed_order(sender, order=None, **kwargs):  # pylint: disable=unused-argument
    """Emit a tracking event when an order is placed."""
    if not (is_segment_configured() and order.total_excl_tax > 0):
        return

    user_tracking_id, lms_client_id = parse_tracking_context(order.user)

    analytics.track(
        user_tracking_id,
        "Completed Order",
        {
            "orderId": order.number,
            "total": str(order.total_excl_tax),
            "currency": order.currency,
            "products": [
                {
                    # For backwards-compatibility with older events the `sku` field is (ab)used to
                    # store the product's `certificate_type`, while the `id` field holds the product's
                    # SKU. Marketing is aware that this approach will not scale once we start selling
                    # products other than courses, and will need to change in the future.
                    "id": line.partner_sku,
                    "sku": line.product.attr.certificate_type,
                    "name": line.product.title,
                    "price": str(line.line_price_excl_tax),
                    "quantity": line.quantity,
                    "category": line.product.get_product_class().name,
                }
                for line in order.lines.all()
            ],
        },
        context={"Google Analytics": {"clientId": lms_client_id}},
    )
Пример #4
0
    def test_flush_with_product(self):
        """
        Verify the method fires 'Product Removed' Segment event with the correct information when basket is not empty
        """
        basket = self._create_basket_with_product()

        properties = translate_basket_line_for_segment(basket.lines.first())
        user_tracking_id, ga_client_id, lms_ip = parse_tracking_context(
            basket.owner)
        context = {
            'ip': lms_ip,
            'Google Analytics': {
                'clientId': ga_client_id
            },
            'page': {
                'url': 'https://testserver.fake/'
            },
        }

        with mock.patch.object(Client, 'track') as mock_track:
            basket.flush()
            mock_track.assert_called_once_with(user_tracking_id,
                                               'Product Removed',
                                               properties,
                                               context=context)
Пример #5
0
    def test_valid_payment_segment_logging(self, mock_track):
        """
        Verify the "Payment Info Entered" Segment event is fired after payment info is validated
        """
        basket = create_basket(owner=self.user, site=self.site)

        mixin = EdxOrderPlacementMixin()
        mixin.payment_processor = DummyProcessor(self.site)
        properties = {'checkout_id': basket.order_number}

        user_tracking_id, lms_client_id, lms_ip = parse_tracking_context(
            self.user)
        context = {
            'ip': lms_ip,
            'Google Analytics': {
                'clientId': lms_client_id
            }
        }

        mixin.handle_payment({}, basket)
        # ensure that the only two Segment events fired are 'Product Added' and 'Payment Info Entered'
        self.assertEqual(mock_track.call_count, 2)
        mock_track.assert_called_with(user_tracking_id,
                                      'Payment Info Entered',
                                      properties,
                                      context=context)
Пример #6
0
def embargo_check(user, site, products):
    """ Checks if the user has access to purchase products by calling the LMS embargo API.

    Args:
        request (object): The current request
        products (list): A list of products to check access against

    Returns:
        Bool
    """
    courses = []
    _, _, ip = parse_tracking_context(user, usage='embargo')

    for product in products:
        # We only are checking Seats
        if product.get_product_class().name == SEAT_PRODUCT_CLASS_NAME:
            courses.append(product.course.id)

    if courses:
        params = {'user': user, 'ip_address': ip, 'course_ids': courses}

        try:
            response = site.siteconfiguration.embargo_api_client.course_access.get(
                **params)
            return response.get('access', True)
        except:  # pylint: disable=bare-except
            # We are going to allow purchase if the API is un-reachable.
            pass

    return True
Пример #7
0
    def test_parse_tracking_context(self):
        """ The method should parse the tracking context on the User object. """
        tracking_context = {
            'ga_client_id': 'test-client-id',
            'lms_user_id': 'foo',
            'lms_ip': '18.0.0.1',
        }
        user = self.create_user(tracking_context=tracking_context)
        expected = (tracking_context['lms_user_id'], tracking_context['ga_client_id'], tracking_context['lms_ip'])
        self.assertEqual(parse_tracking_context(user), expected)

        # If no LMS user ID is provided, we should create one based on the E-Commerce ID
        del tracking_context['lms_user_id']
        user = self.create_user(tracking_context=tracking_context)
        expected = ('ecommerce-{}'.format(user.id), tracking_context['ga_client_id'], tracking_context['lms_ip'])
        self.assertEqual(parse_tracking_context(user), expected)
Пример #8
0
def track_completed_refund(sender, refund=None, **kwargs):  # pylint: disable=unused-argument
    """Emit a tracking event when a refund is completed."""
    if not (is_segment_configured() and refund.total_credit_excl_tax > 0):
        return

    user_tracking_id, lms_client_id, lms_ip = parse_tracking_context(
        refund.user)

    refund.order.site.siteconfiguration.segment_client.track(
        user_tracking_id,
        'Order Refunded',
        {
            'orderId':
            refund.order.number,
            'products': [{
                'id': line.order_line.partner_sku,
                'quantity': line.quantity,
            } for line in refund.lines.all()],
        },
        context={
            'ip': lms_ip,
            'Google Analytics': {
                'clientId': lms_client_id
            }
        },
    )
Пример #9
0
    def test_parse_tracking_context_not_available(self):
        """
        The method should still pull a value for the user_id when there is no tracking context.
        """
        user = self.create_user()
        expected_context = (user.lms_user_id, None, None)

        context = parse_tracking_context(user)
        self.assertEqual(context, expected_context)
Пример #10
0
 def test_parse_tracking_context_not_available(self):
     """
     The method should still pull the user_id from lms_user_id on the User object when there is
     no tracking context.
     """
     user = self.create_user()
     expected = ('test-user-id', None, None)
     with mock.patch('ecommerce.core.models.User.lms_user_id_from_request',
                     return_value='test-user-id'):
         self.assertEqual(parse_tracking_context(user), expected)
Пример #11
0
    def test_payment_not_accepted_segment_logging(self, mock_track):
        """
        Verify if the payment is not accepted, we still log the processor response
        """
        tracking_context = {
            'ga_client_id': 'test-client-id',
            'lms_user_id': 'test-user-id',
            'lms_ip': '127.0.0.1'
        }
        self.user.tracking_context = tracking_context
        self.user.save()

        basket = create_basket(owner=self.user, site=self.site)

        mixin = EdxOrderPlacementMixin()
        mixin.payment_processor = DummyProcessor(self.site)

        user_tracking_id, ga_client_id, lms_ip = parse_tracking_context(
            self.user)
        context = {
            'ip': lms_ip,
            'Google Analytics': {
                'clientId': ga_client_id
            },
            'page': {
                'url': 'https://testserver.fake/'
            },
        }
        with self.assertRaises(Exception):
            mixin.handle_payment({}, basket)

        # Verify the correct events are fired to Segment
        calls = []

        properties = translate_basket_line_for_segment(basket.lines.first())
        properties['cart_id'] = basket.id
        calls.append(
            mock.call(user_tracking_id,
                      'Product Added',
                      properties,
                      context=context))

        properties = {
            'basket_id': basket.id,
            'payment_error': 'Exception',
            'success': False,
            'processor_name': DummyProcessor.NAME,
        }
        calls.append(
            mock.call(user_tracking_id,
                      'Payment Processor Response',
                      properties,
                      context=context))

        mock_track.assert_has_calls(calls)
Пример #12
0
    def test_parse_tracking_context(self):
        """ The method should parse the tracking context on the User object. """
        tracking_context = {
            'ga_client_id': 'test-client-id',
            'lms_user_id': 'foo',
            'lms_ip': '18.0.0.1',
        }

        user = self.create_user(tracking_context=tracking_context)
        expected = (user.lms_user_id, tracking_context['ga_client_id'], tracking_context['lms_ip'])
        self.assertEqual(parse_tracking_context(user), expected)
Пример #13
0
def send_notification(user, commtype_code, context, site):
    """Send different notification mail to the user based on the triggering event.

    Args:
    user(obj): 'User' object to whom email is to send
    commtype_code(str): Communication type code
    context(dict): context to be used in the mail

    """

    tracking_id, client_id, ip = parse_tracking_context(user)

    tracking_pixel = 'https://www.google-analytics.com/collect?v=1&t=event&ec=email&ea=open&tid={tracking_id}' \
                     '&cid={client_id}&uip={ip}'.format(tracking_id=tracking_id, client_id=client_id, ip=ip)
    full_name = user.get_full_name()
    configuration_helpers = site.siteconfiguration.edly_client_theme_branding_settings

    context.update({
        'full_name':
        full_name,
        'site_domain':
        site.domain,
        'platform_name':
        configuration_helpers.get('PLATFORM_NAME', site.name),
        'tracking_pixel':
        tracking_pixel,
        'edly_mailing_address':
        configuration_helpers.get('CONTACT_MAILING_ADDRESS'),
        'edly_copyright_text':
        configuration_helpers.get('EDLY_COPYRIGHT_TEXT'),
        'edly_branding_config':
        configuration_helpers.get('BRANDING'),
        'edly_colors_config':
        get_theme_colors(configuration_helpers)
    })

    try:
        event_type = CommunicationEventType.objects.get(code=commtype_code)
    except CommunicationEventType.DoesNotExist:
        try:
            messages = CommunicationEventType.objects.get_and_render(
                commtype_code, context)
        except Exception:  # pylint: disable=broad-except
            log.error(
                'Unable to locate a DB entry or templates for communication type [%s]. '
                'No notification has been sent.', commtype_code)
            return
    else:
        messages = event_type.get_messages(context)

    if messages and (messages['body'] or messages['html']):
        messages['html'] = transform(messages['html'])
        Dispatcher().dispatch_user_messages(user, messages, site)
Пример #14
0
    def test_parse_tracking_context_missing_lms_user_id(self):
        """ The method should parse the tracking context on the User object. """
        tracking_context = {
            'ga_client_id': 'test-client-id',
            'lms_user_id': 'foo',
            'lms_ip': '18.0.0.1',
        }

        # If no LMS user ID is provided, we should create one based on the E-Commerce ID
        user = self.create_user(tracking_context=tracking_context, lms_user_id=None)
        expected_user_id = ECOM_TRACKING_ID_FMT.format(user.id)

        expected = (expected_user_id, tracking_context['ga_client_id'], tracking_context['lms_ip'])
        self.assertEqual(parse_tracking_context(user), expected)
Пример #15
0
 def test_track_segment_event(self):
     """ The function should fire an event to Segment if the site is properly configured. """
     self.site_configuration.segment_key = 'fake-key'
     self.site_configuration.save()
     user, event, properties = self._get_generic_segment_event_parameters()
     user_tracking_id, ga_client_id, lms_ip = parse_tracking_context(user)
     context = {
         'ip': lms_ip,
         'Google Analytics': {
             'clientId': ga_client_id
         }
     }
     with mock.patch.object(Client, 'track') as mock_track:
         track_segment_event(self.site, user, event, properties)
         mock_track.assert_called_once_with(user_tracking_id, event, properties, context=context)
Пример #16
0
    def test_valid_payment_segment_logging(self, mock_track):
        """
        Verify the "Payment Info Entered" Segment event is fired after payment info is validated
        """
        tracking_context = {'ga_client_id': 'test-client-id', 'lms_user_id': 'test-user-id', 'lms_ip': '127.0.0.1'}
        self.user.tracking_context = tracking_context
        self.user.save()

        basket = create_basket(owner=self.user, site=self.site)

        mixin = EdxOrderPlacementMixin()
        mixin.payment_processor = DummyProcessor(self.site)

        user_tracking_id, ga_client_id, lms_ip = parse_tracking_context(self.user)
        context = {
            'ip': lms_ip,
            'Google Analytics': {
                'clientId': ga_client_id
            },
            'page': {
                'url': 'https://testserver.fake/'
            },
        }

        mixin.handle_payment({}, basket)

        # Verify the correct events are fired to Segment
        calls = []

        properties = translate_basket_line_for_segment(basket.lines.first())
        properties['cart_id'] = basket.id
        calls.append(mock.call(user_tracking_id, 'Product Added', properties, context=context))

        properties = {
            'checkout_id': basket.order_number,
            'step': 1,
            'payment_method': 'Visa | ' + DummyProcessor.NAME,
        }
        calls.append(mock.call(user_tracking_id, 'Checkout Step Completed', properties, context=context))
        properties['step'] = 2
        calls.append(mock.call(user_tracking_id, 'Checkout Step Viewed', properties, context=context))
        calls.append(mock.call(user_tracking_id, 'Checkout Step Completed', properties, context=context))

        properties = {'checkout_id': basket.order_number}
        calls.append(mock.call(user_tracking_id, 'Payment Info Entered', properties, context=context))

        mock_track.assert_has_calls(calls)
Пример #17
0
    def _post_to_enrollment_api(self, data, user):
        enrollment_api_url = get_lms_enrollment_api_url()
        timeout = settings.ENROLLMENT_FULFILLMENT_TIMEOUT
        headers = {
            'Content-Type': 'application/json',
            'X-Edx-Api-Key': settings.EDX_API_KEY
        }

        __, client_id, ip = parse_tracking_context(user)

        if client_id:
            headers['X-Edx-Ga-Client-Id'] = client_id

        if ip:
            headers['X-Forwarded-For'] = ip

        return requests.post(enrollment_api_url, data=json.dumps(data), headers=headers, timeout=timeout)
Пример #18
0
    def _post_to_enrollment_api(self, data, user):
        enrollment_api_url = settings.ENROLLMENT_API_URL
        timeout = settings.ENROLLMENT_FULFILLMENT_TIMEOUT
        headers = {
            'Content-Type': 'application/json',
            'X-Edx-Api-Key': settings.EDX_API_KEY
        }

        __, client_id, ip = parse_tracking_context(user)

        if client_id:
            headers['X-Edx-Ga-Client-Id'] = client_id

        if ip:
            headers['X-Forwarded-For'] = ip

        return requests.post(enrollment_api_url, data=json.dumps(data), headers=headers, timeout=timeout)
Пример #19
0
    def revoke_line(self, line):
        try:
            logger.info('Attempting to revoke fulfillment of Line [%d]...', line.id)

            mode = mode_for_seat(line.product)
            course_key = line.product.attr.course_key
            data = {
                'user': line.order.user.username,
                'is_active': False,
                'mode': mode,
                'course_details': {
                    'course_id': course_key,
                },
            }

            __, client_id = parse_tracking_context(line.order.user)
            response = self._post_to_enrollment_api(data, client_id=client_id)

            if response.status_code == status.HTTP_200_OK:
                audit_log(
                    'line_revoked',
                    order_line_id=line.id,
                    order_number=line.order.number,
                    product_class=line.product.get_product_class().name,
                    course_id=course_key,
                    certificate_type=getattr(line.product.attr, 'certificate_type', ''),
                    user_id=line.order.user.id
                )

                return True
            else:
                # check if the error / message are something we can recover from.
                data = response.json()
                detail = data.get('message', '(No details provided.)')
                if response.status_code == 400 and "Enrollment mode mismatch" in detail:
                    # The user is currently enrolled in different mode than the one
                    # we are refunding an order for.  Don't revoke that enrollment.
                    logger.info('Skipping revocation for line [%d]: %s', line.id, detail)
                    return True
                else:
                    logger.error('Failed to revoke fulfillment of Line [%d]: %s', line.id, detail)
        except Exception:  # pylint: disable=broad-except
            logger.exception('Failed to revoke fulfillment of Line [%d].', line.id)

        return False
Пример #20
0
def track_completed_refund(sender, refund=None, **kwargs):  # pylint: disable=unused-argument
    """Emit a tracking event when a refund is completed."""
    if not (is_segment_configured() and refund.total_credit_excl_tax > 0):
        return

    user_tracking_id, lms_client_id, lms_ip = parse_tracking_context(
        refund.user)

    # Ecommerce transaction reversal, performed by emitting an event which is the inverse of an
    # order completion event emitted previously.
    # See: https://support.google.com/analytics/answer/1037443?hl=en
    refund.order.site.siteconfiguration.segment_client.track(
        user_tracking_id,
        'Completed Order',
        {
            'orderId':
            refund.order.number,
            'total':
            '-{}'.format(refund.total_credit_excl_tax),
            'currency':
            refund.currency,
            'products': [
                {
                    # For backwards-compatibility with older events the `sku` field is (ab)used to
                    # store the product's `certificate_type`, while the `id` field holds the product's
                    # SKU. Marketing is aware that this approach will not scale once we start selling
                    # products other than courses, and will need to change in the future.
                    'id': line.order_line.partner_sku,
                    'sku': mode_for_seat(line.order_line.product),
                    'name': line.order_line.product.course.id,
                    'price': str(line.line_credit_excl_tax),
                    'quantity': -1 * line.quantity,
                    'category':
                    line.order_line.product.get_product_class().name,
                } for line in refund.lines.all()
            ],
        },
        context={
            'ip': lms_ip,
            'Google Analytics': {
                'clientId': lms_client_id
            }
        },
    )
Пример #21
0
def send_notification(user, commtype_code, context, site, recipient=None):
    """Send different notification mail to the user based on the triggering event.

    Args:
    user(obj): 'User' object to whom email is to send
    commtype_code(str): Communication type code
    context(dict): context to be used in the mail
    recipient(str): Email which overrides user.email when set

    """

    tracking_id, client_id, ip = parse_tracking_context(user,
                                                        usage='notification')

    tracking_pixel = 'https://www.google-analytics.com/collect?v=1&t=event&ec=email&ea=open&tid={tracking_id}' \
                     '&cid={client_id}&uip={ip}'.format(tracking_id=tracking_id, client_id=client_id, ip=ip)
    full_name = user.get_full_name()
    context.update({
        'full_name': full_name,
        'site_domain': site.domain,
        'platform_name': site.name,
        'tracking_pixel': tracking_pixel,
    })

    try:
        event_type = CommunicationEventType.objects.get(code=commtype_code)
    except CommunicationEventType.DoesNotExist:
        try:
            messages = CommunicationEventType.objects.get_and_render(
                commtype_code, context)
        except Exception:  # pylint: disable=broad-except
            log.error(
                'Unable to locate a DB entry or templates for communication type [%s]. '
                'No notification has been sent.', commtype_code)
            return
    else:
        messages = event_type.get_messages(context)

    if messages and (messages['body'] or messages['html']):
        messages['html'] = transform(messages['html'])
        Dispatcher().dispatch_user_messages(user, messages, site, recipient)
Пример #22
0
def track_completed_order(sender, order=None, **kwargs):  # pylint: disable=unused-argument
    """Emit a tracking event when an order is placed."""
    if not (is_segment_configured() and order.total_excl_tax > 0):
        return

    user_tracking_id, lms_client_id, lms_ip = parse_tracking_context(
        order.user)

    order.site.siteconfiguration.segment_client.track(
        user_tracking_id,
        'Completed Order',
        {
            'orderId':
            order.number,
            'total':
            str(order.total_excl_tax),
            'currency':
            order.currency,
            'products': [
                {
                    # For backwards-compatibility with older events the `sku` field is (ab)used to
                    # store the product's `certificate_type`, while the `id` field holds the product's
                    # SKU. Marketing is aware that this approach will not scale once we start selling
                    # products other than courses, and will need to change in the future.
                    'id': line.partner_sku,
                    'sku': mode_for_seat(line.product),
                    'name': line.product.course.id,
                    'price': str(line.line_price_excl_tax),
                    'quantity': line.quantity,
                    'category': line.product.get_product_class().name,
                } for line in order.lines.all()
            ],
        },
        context={
            'ip': lms_ip,
            'Google Analytics': {
                'clientId': lms_client_id
            }
        },
    )
Пример #23
0
def track_completed_refund(sender, refund=None, **kwargs):  # pylint: disable=unused-argument
    """Emit a tracking event when a refund is completed."""
    if not (is_segment_configured() and refund.total_credit_excl_tax > 0):
        return

    user_tracking_id, lms_client_id, lms_ip = parse_tracking_context(refund.user)

    # Ecommerce transaction reversal, performed by emitting an event which is the inverse of an
    # order completion event emitted previously.
    # See: https://support.google.com/analytics/answer/1037443?hl=en
    refund.order.site.siteconfiguration.segment_client.track(
        user_tracking_id,
        'Completed Order',
        {
            'orderId': refund.order.number,
            'total': '-{}'.format(refund.total_credit_excl_tax),
            'currency': refund.currency,
            'products': [
                {
                    # For backwards-compatibility with older events the `sku` field is (ab)used to
                    # store the product's `certificate_type`, while the `id` field holds the product's
                    # SKU. Marketing is aware that this approach will not scale once we start selling
                    # products other than courses, and will need to change in the future.
                    'id': line.order_line.partner_sku,
                    'sku': mode_for_seat(line.order_line.product),
                    'name': line.order_line.product.course.id,
                    'price': str(line.line_credit_excl_tax),
                    'quantity': -1 * line.quantity,
                    'category': line.order_line.product.get_product_class().name,
                } for line in refund.lines.all()
            ],
        },
        context={
            'ip': lms_ip,
            'Google Analytics': {
                'clientId': lms_client_id
            }
        },
    )
Пример #24
0
def send_notification(user, commtype_code, context, site):
    """Send different notification mail to the user based on the triggering event.

    Args:
    user(obj): 'User' object to whom email is to send
    commtype_code(str): Communication type code
    context(dict): context to be used in the mail

    """

    tracking_id, client_id, ip = parse_tracking_context(user)

    tracking_pixel = 'https://www.google-analytics.com/collect?v=1&t=event&ec=email&ea=open&tid={tracking_id}' \
                     '&cid={client_id}&uip={ip}'.format(tracking_id=tracking_id, client_id=client_id, ip=ip)
    full_name = user.get_full_name()
    context.update({
        'full_name': full_name,
        'site_domain': site.domain,
        'platform_name': site.name,
        'tracking_pixel': tracking_pixel,
    })

    try:
        event_type = CommunicationEventType.objects.get(code=commtype_code)
    except CommunicationEventType.DoesNotExist:
        try:
            messages = CommunicationEventType.objects.get_and_render(commtype_code, context)
        except Exception:  # pylint: disable=broad-except
            log.error('Unable to locate a DB entry or templates for communication type [%s]. '
                      'No notification has been sent.', commtype_code)
            return
    else:
        messages = event_type.get_messages(context)

    if messages and (messages['body'] or messages['html']):
        messages['html'] = transform(messages['html'])
        Dispatcher().dispatch_user_messages(user, messages, site)
Пример #25
0
def send_notification(user, commtype_code, context):
    """Send different notification mail to the user based on the triggering event.

    Args:
    user(obj): 'User' object to whom email is to send
    commtype_code(str): Communication type code
    context(dict): context to be used in the mail

    """

    tracking_id, client_id, ip = parse_tracking_context(user)

    tracking_pixel = (
        "https://www.google-analytics.com/collect?v=1&t=event&ec=email&ea=open&tid={tracking_id}"
        "&cid={client_id}&uip={ip}".format(tracking_id=tracking_id, client_id=client_id, ip=ip)
    )
    full_name = user.get_full_name()
    context.update({"full_name": full_name, "platform_name": settings.PLATFORM_NAME, "tracking_pixel": tracking_pixel})

    try:
        event_type = CommunicationEventType.objects.get(code=commtype_code)
    except CommunicationEventType.DoesNotExist:
        try:
            messages = CommunicationEventType.objects.get_and_render(commtype_code, context)
        except Exception:  # pylint: disable=broad-except
            log.error(
                "Unable to locate a DB entry or templates for communication type [%s]. "
                "No notification has been sent.",
                commtype_code,
            )
            return
    else:
        messages = event_type.get_messages(context)

    if messages and (messages["body"] or messages["html"]):
        messages["html"] = transform(messages["html"])
        Dispatcher().dispatch_user_messages(user, messages)
Пример #26
0
    def test_track_segment_event(self):
        """ The function should fire an event to Segment if the site is properly configured. """
        properties = {'key': 'value'}
        self.site_configuration.segment_key = 'fake-key'
        self.site_configuration.save()
        user = self.create_user(
            tracking_context={
                'ga_client_id': 'test-client-id',
                'lms_user_id': 'foo',
                'lms_ip': '18.0.0.1',
            }
        )
        user_tracking_id, ga_client_id, lms_ip = parse_tracking_context(user)
        context = {
            'ip': lms_ip,
            'Google Analytics': {
                'clientId': ga_client_id
            }
        }
        event = 'foo'

        with mock.patch.object(Client, 'track') as mock_track:
            track_segment_event(self.site, user, event, properties)
            mock_track.assert_called_once_with(user_tracking_id, event, properties, context=context)
Пример #27
0
    def fulfill_product(self, order, lines):
        """ Fulfills the purchase of a 'seat' by enrolling the associated student.

        Uses the order and the lines to determine which courses to enroll a student in, and with certain
        certificate types. May result in an error if the Enrollment API cannot be reached, or if there is
        additional business logic errors when trying to enroll the student.

        Args:
            order (Order): The Order associated with the lines to be fulfilled. The user associated with the order
                is presumed to be the student to enroll in a course.
            lines (List of Lines): Order Lines, associated with purchased products in an Order. These should only
                be "Seat" products.

        Returns:
            The original set of lines, with new statuses set based on the success or failure of fulfillment.

        """
        logger.info("Attempting to fulfill 'Seat' product types for order [%s]", order.number)

        enrollment_api_url = getattr(settings, 'ENROLLMENT_API_URL', None)
        api_key = getattr(settings, 'EDX_API_KEY', None)
        if not (enrollment_api_url and api_key):
            logger.error(
                'ENROLLMENT_API_URL and EDX_API_KEY must be set to use the EnrollmentFulfillmentModule'
            )
            for line in lines:
                line.set_status(LINE.FULFILLMENT_CONFIGURATION_ERROR)

            return order, lines

        for line in lines:
            try:
                mode = mode_for_seat(line.product)
                course_key = line.product.attr.course_key
            except AttributeError:
                logger.error("Supported Seat Product does not have required attributes, [certificate_type, course_key]")
                line.set_status(LINE.FULFILLMENT_CONFIGURATION_ERROR)
                continue
            try:
                provider = line.product.attr.credit_provider
            except AttributeError:
                logger.info("Supported Seat Product does not have a credit provider.")
                provider = None

            data = {
                'user': order.user.username,
                'is_active': True,
                'mode': mode,
                'course_details': {
                    'course_id': course_key
                },
                'enrollment_attributes': []
            }
            if provider:
                data['enrollment_attributes'].append(
                    {
                        'namespace': 'credit',
                        'name': 'provider_id',
                        'value': provider
                    }
                )
            try:
                __, client_id = parse_tracking_context(order.user)
                response = self._post_to_enrollment_api(data, client_id=client_id)

                if response.status_code == status.HTTP_200_OK:
                    line.set_status(LINE.COMPLETE)

                    audit_log(
                        'line_fulfilled',
                        order_line_id=line.id,
                        order_number=order.number,
                        product_class=line.product.get_product_class().name,
                        course_id=course_key,
                        mode=mode,
                        user_id=order.user.id,
                        credit_provider=provider,
                    )
                else:
                    try:
                        data = response.json()
                        reason = data.get('message')
                    except Exception:  # pylint: disable=broad-except
                        reason = '(No detail provided.)'

                    logger.error(
                        "Unable to fulfill line [%d] of order [%s] due to a server-side error: %s", line.id,
                        order.number, reason
                    )
                    line.set_status(LINE.FULFILLMENT_SERVER_ERROR)
            except ConnectionError:
                logger.error(
                    "Unable to fulfill line [%d] of order [%s] due to a network problem", line.id, order.number
                )
                line.set_status(LINE.FULFILLMENT_NETWORK_ERROR)
            except Timeout:
                logger.error(
                    "Unable to fulfill line [%d] of order [%s] due to a request time out", line.id, order.number
                )
                line.set_status(LINE.FULFILLMENT_TIMEOUT_ERROR)
        logger.info("Finished fulfilling 'Seat' product types for order [%s]", order.number)
        return order, lines