예제 #1
0
    def test_generate_sku_for_coupon(self):
        """Verify the method generates a SKU for a coupon."""
        data = {
            'partner': self.partner,
            'benefit_type': Benefit.PERCENTAGE,
            'benefit_value': 100,
            'catalog': self.catalog,
            'end_date': datetime.date(2020, 1, 1),
            'code': '',
            'quantity': 5,
            'start_date': datetime.date(2015, 1, 1),
            'voucher_type': Voucher.SINGLE_USE
        }
        coupon = CouponViewSet().create_coupon_product(
            title='Test coupon',
            price=100,
            data=data
        )

        _hash = ' '.join((
            unicode(coupon.id),
            unicode(self.catalog.id),
            str(self.partner.id)
        ))
        _hash = md5(_hash.lower()).hexdigest()[-7:]
        expected = _hash.upper()
        actual = generate_sku(coupon, self.partner, catalog=self.catalog)
        self.assertEqual(actual, expected)
예제 #2
0
    def test_generate_sku_for_course_seat(self):
        """Verify the method generates a SKU for a course seat."""
        course_id = 'sku/test/course'
        course = Course.objects.create(id=course_id, name='Test Course')
        certificate_type = 'honor'
        product = course.create_or_update_seat(certificate_type, False, 0)

        _hash = md5(u'{} {} {}'.format(certificate_type, course_id, 'False')).hexdigest()[-7:]
        expected = _hash.upper()
        actual = generate_sku(product)
        self.assertEqual(actual, expected)
예제 #3
0
 def test_generate_sku_for_coupon(self):
     """Verify the method generates a SKU for a coupon."""
     coupon = self.create_coupon(partner=self.partner, catalog=self.catalog)
     _hash = ' '.join((
         unicode(coupon.id),
         str(self.partner.id)
     ))
     digest = md5(_hash.lower()).hexdigest()[-7:]
     expected = digest.upper()
     actual = generate_sku(coupon, self.partner)
     self.assertEqual(actual, expected)
예제 #4
0
    def test_generate_sku_for_course_seat(self):
        """Verify the method generates a SKU for a course seat."""
        course_id = 'sku/test/course'
        course = Course.objects.create(id=course_id, name='Test Course')
        certificate_type = 'honor'
        product = course.create_or_update_seat(certificate_type, False, 0, self.partner)

        _hash = '{} {} {} {} {}'.format(certificate_type, course_id, 'False', '', self.partner.id)
        _hash = md5(_hash.lower()).hexdigest()[-7:]
        # verify that generated sku has partner 'short_code' as prefix
        expected = _hash.upper()
        actual = generate_sku(product, self.partner)
        self.assertEqual(actual, expected)
예제 #5
0
    def _create_or_update_enrollment_code(self, seat_type, id_verification_required, partner, price):
        """
        Creates an enrollment code product and corresponding stock record for the specified seat.
        Includes course ID and seat type as product attributes.

        Args:
            seat_type (str): Seat type.
            partner (Partner): Seat provider set in the stock record.
            price (Decimal): Price of the seat.

        Returns:
            Enrollment code product.
        """

        enrollment_code_product_class = ProductClass.objects.get(name=ENROLLMENT_CODE_PRODUCT_CLASS_NAME)
        enrollment_code = self.enrollment_code_product
        if not enrollment_code:
            title = 'Enrollment code for {seat_type} seat in {course_name}'.format(
                seat_type=seat_type,
                course_name=self.name
            )
            enrollment_code = Product(
                title=title,
                product_class=enrollment_code_product_class,
                course=self
            )
        enrollment_code.attr.course_key = self.id
        enrollment_code.attr.seat_type = seat_type
        enrollment_code.attr.id_verification_required = id_verification_required
        enrollment_code.save()

        try:
            stock_record = StockRecord.objects.get(product=enrollment_code, partner=partner)
        except StockRecord.DoesNotExist:
            enrollment_code_sku = generate_sku(enrollment_code, partner)
            stock_record = StockRecord(
                product=enrollment_code,
                partner=partner,
                partner_sku=enrollment_code_sku
            )

        stock_record.price_excl_tax = price
        stock_record.price_currency = settings.OSCAR_DEFAULT_CURRENCY
        stock_record.save()

        return enrollment_code
예제 #6
0
    def create_or_update_seat(self, certificate_type, id_verification_required, price, partner,
                              credit_provider=None, expires=None, credit_hours=None):
        """
        Creates course seat products.

        Returns:
            Product:  The seat that has been created or updated.
        """
        certificate_type = certificate_type.lower()
        course_id = unicode(self.id)

        if certificate_type == self.certificate_type_for_mode('audit'):
            # Yields a match if attribute names do not include 'certificate_type'.
            certificate_type_query = ~Q(attributes__name='certificate_type')
        else:
            # Yields a match if attribute with name 'certificate_type' matches provided value.
            certificate_type_query = Q(
                attributes__name='certificate_type',
                attribute_values__value_text=certificate_type
            )

        id_verification_required_query = Q(
            attributes__name='id_verification_required',
            attribute_values__value_boolean=id_verification_required
        )

        if credit_provider is None:
            # Yields a match if attribute names do not include 'credit_provider'.
            credit_provider_query = ~Q(attributes__name='credit_provider')
        else:
            # Yields a match if attribute with name 'credit_provider' matches provided value.
            credit_provider_query = Q(
                attributes__name='credit_provider',
                attribute_values__value_text=credit_provider
            )

        try:
            seat = self.seat_products.filter(
                certificate_type_query
            ).filter(
                id_verification_required_query
            ).get(
                credit_provider_query
            )

            logger.info(
                'Retrieved course seat child product with certificate type [%s] for [%s] from database.',
                certificate_type,
                course_id
            )
        except Product.DoesNotExist:
            seat = Product()
            logger.info(
                'Course seat product with certificate type [%s] for [%s] does not exist. Instantiated a new instance.',
                certificate_type,
                course_id
            )

        seat.course = self
        seat.structure = Product.CHILD
        seat.parent = self.parent_seat_product
        seat.is_discountable = True
        seat.title = self._get_course_seat_name(certificate_type, id_verification_required)
        seat.expires = expires

        # If a ProductAttribute is saved with a value of None or the empty string, the ProductAttribute is deleted.
        # As a consequence, Seats derived from a migrated "audit" mode do not have a certificate_type attribute.
        seat.attr.certificate_type = certificate_type
        seat.attr.course_key = course_id
        seat.attr.id_verification_required = id_verification_required

        if credit_provider:
            seat.attr.credit_provider = credit_provider

        if credit_hours:
            seat.attr.credit_hours = credit_hours

        seat.save()

        try:
            stock_record = StockRecord.objects.get(product=seat, partner=partner)
            logger.info(
                'Retrieved course seat product stock record with certificate type [%s] for [%s] from database.',
                certificate_type,
                course_id
            )
        except StockRecord.DoesNotExist:
            partner_sku = generate_sku(seat, partner)
            stock_record = StockRecord(product=seat, partner=partner, partner_sku=partner_sku)
            logger.info(
                'Course seat product stock record with certificate type [%s] for [%s] does not exist. '
                'Instantiated a new instance.',
                certificate_type,
                course_id
            )

        stock_record.price_excl_tax = price

        # TODO Expose via setting
        stock_record.price_currency = 'USD'
        stock_record.save()

        return seat
예제 #7
0
    def create_coupon_product(self, title, price, data):
        """Creates a coupon product and a stock record for it.

        Arguments:
            title (str): The name of the coupon.
            price (int): The price of the coupon(s).
            data (dict): Contains data needed to create vouchers,SKU and UPC:
                - partner (User)
                - benefit_type (str)
                - benefit_value (int)
                - catalog (Catalog)
                - end_date (Datetime)
                - code (str)
                - quantity (int)
                - start_date (Datetime)
                - voucher_type (str)
                - categories (list of Category objects)

        Returns:
            A coupon product object.

        Raises:
            IntegrityError: An error occured when create_vouchers method returns
                            an IntegrityError exception
            ValidationError: An error occured clean() validation method returns
                             a ValidationError exception
        """
        coupon_slug = generate_coupon_slug(title=title, catalog=data['catalog'], partner=data['partner'])

        product_class = ProductClass.objects.get(slug='coupon')
        coupon_product, __ = Product.objects.get_or_create(
            title=title,
            product_class=product_class,
            slug=coupon_slug
        )

        for category in data['categories']:
            ProductCategory.objects.get_or_create(product=coupon_product, category=category)

        # Vouchers are created during order and not fulfillment like usual
        # because we want vouchers to be part of the line in the order.
        try:
            create_vouchers(
                name=title,
                benefit_type=data['benefit_type'],
                benefit_value=Decimal(data['benefit_value']),
                catalog=data['catalog'],
                coupon=coupon_product,
                end_datetime=data['end_date'],
                code=data['code'] or None,
                quantity=int(data['quantity']),
                start_datetime=data['start_date'],
                voucher_type=data['voucher_type']
            )
        except IntegrityError as ex:
            logger.exception('Failed to create vouchers for [%s] coupon.', coupon_product.title)
            raise IntegrityError(ex)  # pylint: disable=nonstandard-exception

        coupon_vouchers = CouponVouchers.objects.get(coupon=coupon_product)

        coupon_product.attr.coupon_vouchers = coupon_vouchers
        coupon_product.save()

        sku = generate_sku(
            product=coupon_product,
            partner=data['partner'],
            catalog=data['catalog'],
        )

        stock_record, __ = StockRecord.objects.get_or_create(
            product=coupon_product,
            partner=data['partner'],
            partner_sku=sku
        )
        stock_record.price_currency = 'USD'
        stock_record.price_excl_tax = price
        stock_record.save()

        return coupon_product
예제 #8
0
 def test_generate_sku_with_unexpected_product_class(self):
     """Verify the method raises an exception for unsupported product class."""
     product = ProductFactory()
     with self.assertRaises(Exception):
         generate_sku(product, self.partner)
예제 #9
0
 def test_generate_sku_with_unexpected_product_class(self):
     """Verify the method raises an exception for unsupported product class."""
     product = ProductFactory()
     with self.assertRaises(Exception):
         generate_sku(product, self.partner)
예제 #10
0
    def create_coupon_product(self, title, price, data):
        """Creates a coupon product and a stock record for it.

        Arguments:
            title (str): The name of the coupon.
            price (int): The price of the coupon(s).
            data (dict): Contains data needed to create vouchers,SKU and UPC:
                - partner (User)
                - benefit_type (str)
                - benefit_value (int)
                - catalog (Catalog)
                - end_date (Datetime)
                - code (str)
                - quantity (int)
                - start_date (Datetime)
                - voucher_type (str)
                - categories (list of Category objects)
                - note (str)
                - max_uses (int)
                - catalog_query (str)
                - course_seat_types (str)

        Returns:
            A coupon product object.
        """

        product_class = ProductClass.objects.get(slug='coupon')
        coupon_product = Product.objects.create(title=title, product_class=product_class)

        self.assign_categories_to_coupon(coupon=coupon_product, categories=data['categories'])

        # Vouchers are created during order and not fulfillment like usual
        # because we want vouchers to be part of the line in the order.
        create_vouchers(
            name=title,
            benefit_type=data['benefit_type'],
            benefit_value=Decimal(data['benefit_value']),
            catalog=data['catalog'],
            coupon=coupon_product,
            end_datetime=data['end_date'],
            code=data['code'] or None,
            quantity=int(data['quantity']),
            start_datetime=data['start_date'],
            voucher_type=data['voucher_type'],
            max_uses=data['max_uses'],
            catalog_query=data['catalog_query'],
            course_seat_types=data['course_seat_types']
        )

        coupon_vouchers = CouponVouchers.objects.get(coupon=coupon_product)

        coupon_product.attr.coupon_vouchers = coupon_vouchers
        coupon_product.attr.note = data['note']
        coupon_product.save()

        sku = generate_sku(product=coupon_product, partner=data['partner'])
        StockRecord.objects.update_or_create(
            product=coupon_product,
            partner=data['partner'],
            partner_sku=sku,
            defaults={
                'price_currency': 'USD',
                'price_excl_tax': price
            }
        )

        return coupon_product
예제 #11
0
    def create_or_update_seat(self, certificate_type, id_verification_required, price, partner,
                              credit_provider=None, expires=None, credit_hours=None, remove_stale_modes=True):
        """
        Creates course seat products.

        Returns:
            Product:  The seat that has been created or updated.
        """
        certificate_type = certificate_type.lower()
        course_id = unicode(self.id)

        if certificate_type == self.certificate_type_for_mode('audit'):
            # Yields a match if attribute names do not include 'certificate_type'.
            certificate_type_query = ~Q(attributes__name='certificate_type')
        else:
            # Yields a match if attribute with name 'certificate_type' matches provided value.
            certificate_type_query = Q(
                attributes__name='certificate_type',
                attribute_values__value_text=certificate_type
            )

        id_verification_required_query = Q(
            attributes__name='id_verification_required',
            attribute_values__value_boolean=id_verification_required
        )

        if credit_provider is None:
            # Yields a match if attribute names do not include 'credit_provider'.
            credit_provider_query = ~Q(attributes__name='credit_provider')
        else:
            # Yields a match if attribute with name 'credit_provider' matches provided value.
            credit_provider_query = Q(
                attributes__name='credit_provider',
                attribute_values__value_text=credit_provider
            )

        seats = self.seat_products.filter(certificate_type_query)
        try:
            seat = seats.filter(
                id_verification_required_query
            ).get(
                credit_provider_query
            )

            logger.info(
                'Retrieved course seat child product with certificate type [%s] for [%s] from database.',
                certificate_type,
                course_id
            )
        except Product.DoesNotExist:
            seat = Product()
            logger.info(
                'Course seat product with certificate type [%s] for [%s] does not exist. Instantiated a new instance.',
                certificate_type,
                course_id
            )

        seat.course = self
        seat.structure = Product.CHILD
        seat.parent = self.parent_seat_product
        seat.is_discountable = True
        seat.title = self.get_course_seat_name(certificate_type, id_verification_required)
        seat.expires = expires

        # If a ProductAttribute is saved with a value of None or the empty string, the ProductAttribute is deleted.
        # As a consequence, Seats derived from a migrated "audit" mode do not have a certificate_type attribute.
        seat.attr.certificate_type = certificate_type
        seat.attr.course_key = course_id
        seat.attr.id_verification_required = id_verification_required

        if waffle.switch_is_active(ENROLLMENT_CODE_SWITCH) and certificate_type in ENROLLMENT_CODE_SEAT_TYPES:
            self._create_or_update_enrollment_code(certificate_type, id_verification_required, partner, price)

        if credit_provider:
            seat.attr.credit_provider = credit_provider

        if credit_hours:
            seat.attr.credit_hours = credit_hours

        seat.save()

        try:
            stock_record = StockRecord.objects.get(product=seat, partner=partner)
            logger.info(
                'Retrieved course seat product stock record with certificate type [%s] for [%s] from database.',
                certificate_type,
                course_id
            )
        except StockRecord.DoesNotExist:
            partner_sku = generate_sku(seat, partner)
            stock_record = StockRecord(product=seat, partner=partner, partner_sku=partner_sku)
            logger.info(
                'Course seat product stock record with certificate type [%s] for [%s] does not exist. '
                'Instantiated a new instance.',
                certificate_type,
                course_id
            )

        stock_record.price_excl_tax = price
        stock_record.price_currency = settings.OSCAR_DEFAULT_CURRENCY
        stock_record.save()

        if remove_stale_modes and self.certificate_type_for_mode(certificate_type) == 'professional':
            id_verification_required_query = Q(
                attributes__name='id_verification_required',
                attribute_values__value_boolean=not id_verification_required
            )

            # Delete seats with a different verification requirement, assuming the seats
            # have not been purchased.
            seats.annotate(orders=Count('line')).filter(
                id_verification_required_query,
                orders=0
            ).delete()

        return seat
예제 #12
0
    def create_or_update_seat(self,
                              certificate_type,
                              id_verification_required,
                              price,
                              partner,
                              credit_provider=None,
                              expires=None,
                              credit_hours=None,
                              remove_stale_modes=True):
        """
        Creates course seat products.

        Returns:
            Product:  The seat that has been created or updated.
        """
        certificate_type = certificate_type.lower()
        course_id = unicode(self.id)

        if certificate_type == self.certificate_type_for_mode('audit'):
            # Yields a match if attribute names do not include 'certificate_type'.
            certificate_type_query = ~Q(attributes__name='certificate_type')
        else:
            # Yields a match if attribute with name 'certificate_type' matches provided value.
            certificate_type_query = Q(
                attributes__name='certificate_type',
                attribute_values__value_text=certificate_type)

        id_verification_required_query = Q(
            attributes__name='id_verification_required',
            attribute_values__value_boolean=id_verification_required)

        if credit_provider is None:
            # Yields a match if attribute names do not include 'credit_provider'.
            credit_provider_query = ~Q(attributes__name='credit_provider')
        else:
            # Yields a match if attribute with name 'credit_provider' matches provided value.
            credit_provider_query = Q(
                attributes__name='credit_provider',
                attribute_values__value_text=credit_provider)

        seats = self.seat_products.filter(certificate_type_query)
        try:
            seat = seats.filter(id_verification_required_query).get(
                credit_provider_query)

            logger.info(
                'Retrieved course seat child product with certificate type [%s] for [%s] from database.',
                certificate_type, course_id)
        except Product.DoesNotExist:
            seat = Product()
            logger.info(
                'Course seat product with certificate type [%s] for [%s] does not exist. Instantiated a new instance.',
                certificate_type, course_id)

        seat.course = self
        seat.structure = Product.CHILD
        seat.parent = self.parent_seat_product
        seat.is_discountable = True
        seat.title = self.get_course_seat_name(certificate_type,
                                               id_verification_required)
        seat.expires = expires

        # If a ProductAttribute is saved with a value of None or the empty string, the ProductAttribute is deleted.
        # As a consequence, Seats derived from a migrated "audit" mode do not have a certificate_type attribute.
        seat.attr.certificate_type = certificate_type
        seat.attr.course_key = course_id
        seat.attr.id_verification_required = id_verification_required

        if waffle.switch_is_active(
                ENROLLMENT_CODE_SWITCH
        ) and certificate_type in ENROLLMENT_CODE_SEAT_TYPES:
            self._create_or_update_enrollment_code(certificate_type,
                                                   id_verification_required,
                                                   partner, price)

        if credit_provider:
            seat.attr.credit_provider = credit_provider

        if credit_hours:
            seat.attr.credit_hours = credit_hours

        seat.save()

        try:
            stock_record = StockRecord.objects.get(product=seat,
                                                   partner=partner)
            logger.info(
                'Retrieved course seat product stock record with certificate type [%s] for [%s] from database.',
                certificate_type, course_id)
        except StockRecord.DoesNotExist:
            partner_sku = generate_sku(seat, partner)
            stock_record = StockRecord(product=seat,
                                       partner=partner,
                                       partner_sku=partner_sku)
            logger.info(
                'Course seat product stock record with certificate type [%s] for [%s] does not exist. '
                'Instantiated a new instance.', certificate_type, course_id)

        stock_record.price_excl_tax = price
        stock_record.price_currency = settings.OSCAR_DEFAULT_CURRENCY
        stock_record.save()

        if remove_stale_modes and self.certificate_type_for_mode(
                certificate_type) == 'professional':
            id_verification_required_query = Q(
                attributes__name='id_verification_required',
                attribute_values__value_boolean=not id_verification_required)

            # Delete seats with a different verification requirement, assuming the seats
            # have not been purchased.
            seats.annotate(orders=Count('line')).filter(
                id_verification_required_query, orders=0).delete()

        return seat
예제 #13
0
    def create_coupon_product(self, title, price, data):
        """Creates a coupon product and a stock record for it.

        Arguments:
            title (str): The name of the coupon.
            price (int): The price of the coupon(s).
            data (dict): Contains data needed to create vouchers,SKU and UPC:
                - partner (User)
                - benefit_type (str)
                - benefit_value (int)
                - catalog (Catalog)
                - end_date (Datetime)
                - code (str)
                - quantity (int)
                - start_date (Datetime)
                - voucher_type (str)
                - categories (list of Category objects)
                - note (str)
                - max_uses (int)

        Returns:
            A coupon product object.

        Raises:
            IntegrityError: An error occured when create_vouchers method returns
                            an IntegrityError exception
        """
        coupon_slug = generate_coupon_slug(title=title, catalog=data['catalog'], partner=data['partner'])

        product_class = ProductClass.objects.get(slug='coupon')
        coupon_product, __ = Product.objects.get_or_create(
            title=title,
            product_class=product_class,
            slug=coupon_slug
        )

        self.assign_categories_to_coupon(coupon=coupon_product, categories=data['categories'])

        # Vouchers are created during order and not fulfillment like usual
        # because we want vouchers to be part of the line in the order.
        try:
            create_vouchers(
                name=title,
                benefit_type=data['benefit_type'],
                benefit_value=Decimal(data['benefit_value']),
                catalog=data['catalog'],
                coupon=coupon_product,
                end_datetime=data['end_date'],
                code=data['code'] or None,
                quantity=int(data['quantity']),
                start_datetime=data['start_date'],
                voucher_type=data['voucher_type'],
                max_uses=data['max_uses'],
                coupon_id=coupon_product.id
            )
        except IntegrityError as ex:
            logger.exception('Failed to create vouchers for [%s] coupon.', coupon_product.title)
            raise IntegrityError(ex)  # pylint: disable=nonstandard-exception

        coupon_vouchers = CouponVouchers.objects.get(coupon=coupon_product)

        coupon_product.attr.coupon_vouchers = coupon_vouchers
        coupon_product.attr.note = data['note']
        coupon_product.save()

        sku = generate_sku(
            product=coupon_product,
            partner=data['partner'],
            catalog=data['catalog'],
        )

        stock_record, __ = StockRecord.objects.get_or_create(
            product=coupon_product,
            partner=data['partner'],
            partner_sku=sku
        )
        stock_record.price_currency = 'USD'
        stock_record.price_excl_tax = price
        stock_record.save()

        return coupon_product
예제 #14
0
    def create_or_update_seat(
        self,
        certificate_type,
        id_verification_required,
        price,
        credit_provider=None,
        expires=None,
        credit_hours=None,
        remove_stale_modes=True,
        create_enrollment_code=False,
        sku=None,
    ):
        """
        Creates and updates course seat products.
        IMPORTANT: Requires the Partner sku (from the stock record) to be passed in for updates.

        Arguments:
            certificate_type(str): The seat type.
            id_verification_required(bool): Whether an ID verification is required.
            price(int): Price of the seat.
            partner(Partner): Site partner.

        Optional arguments:
            credit_provider(str): Name of the organization that provides the credit
            expires(datetime): Date when the seat type expires.
            credit_hours(int): Number of credit hours provided.
            remove_stale_modes(bool): Remove stale modes.
            create_enrollment_code(bool): Whether an enrollment code is created in addition to the seat.
            sku(str): The partner_sku for the product stored as part of the Stock Record. This is used
                to perform a GET on the seat as a unique identifier both Ecommerce and Discovery know about.

        Returns:
            Product:  The seat that has been created or updated.
        """
        certificate_type = certificate_type.lower()
        course_id = six.text_type(self.id)

        try:
            product_id = StockRecord.objects.get(
                partner_sku=sku, partner=self.partner).product_id
            seat = self.seat_products.get(id=product_id)
            logger.info(
                'Retrieved course seat child product with certificate type [%s] for [%s] from database.',
                certificate_type, course_id)
        except (StockRecord.DoesNotExist, Product.DoesNotExist):
            seat = Product()
            logger.info(
                'Course seat product with certificate type [%s] for [%s] does not exist. Attempted look up using sku '
                '[%s]. Instantiated a new instance.', certificate_type,
                course_id, sku)

        seat.course = self
        seat.structure = Product.CHILD
        seat.parent = self.parent_seat_product
        seat.is_discountable = True
        seat.expires = expires

        id_verification_required_query = Q(
            attributes__name='id_verification_required',
            attribute_values__value_boolean=id_verification_required)
        seat.title = self.get_course_seat_name(certificate_type,
                                               id_verification_required)

        seat.save()

        # If a ProductAttribute is saved with a value of None or the empty string, the ProductAttribute is deleted.
        # As a consequence, Seats derived from a migrated "audit" mode do not have a certificate_type attribute.
        seat.attr.certificate_type = certificate_type
        seat.attr.course_key = course_id
        seat.attr.id_verification_required = id_verification_required
        if certificate_type in ENROLLMENT_CODE_SEAT_TYPES and create_enrollment_code:
            self._create_or_update_enrollment_code(certificate_type,
                                                   id_verification_required,
                                                   self.partner, price,
                                                   expires)

        if credit_provider:
            seat.attr.credit_provider = credit_provider

        if credit_hours:
            seat.attr.credit_hours = credit_hours

        seat.attr.save()

        try:
            stock_record = StockRecord.objects.get(product=seat,
                                                   partner=self.partner)
            logger.info(
                'Retrieved course seat product stock record with certificate type [%s] for [%s] from database.',
                certificate_type, course_id)
        except StockRecord.DoesNotExist:
            partner_sku = generate_sku(seat, self.partner)
            stock_record = StockRecord(product=seat,
                                       partner=self.partner,
                                       partner_sku=partner_sku)
            logger.info(
                'Course seat product stock record with certificate type [%s] for [%s] does not exist. '
                'Instantiated a new instance.', certificate_type, course_id)

        stock_record.price_excl_tax = price
        stock_record.price_currency = settings.OSCAR_DEFAULT_CURRENCY
        stock_record.save()

        if remove_stale_modes and self.certificate_type_for_mode(
                certificate_type) == 'professional':
            id_verification_required_query = Q(
                attributes__name='id_verification_required',
                attribute_values__value_boolean=not id_verification_required)
            certificate_type_query = Q(
                attributes__name='certificate_type',
                attribute_values__value_text=certificate_type)

            # Delete seats with a different verification requirement, assuming the seats
            # have not been purchased.
            self.seat_products.filter(certificate_type_query).annotate(
                orders=Count('line')).filter(id_verification_required_query,
                                             orders=0).delete()

        return seat
예제 #15
0
    def create_or_update_seat(self, certificate_type, id_verification_required, price, credit_provider=None,
                              expires=None, credit_hours=None):
        """
        Creates course seat products.

        Returns:
            Product:  The seat that has been created or updated.
        """

        certificate_type = certificate_type.lower()
        course_id = unicode(self.id)

        slugs = []
        slug = 'child-cs-{}-{}'.format(certificate_type, slugify(course_id))

        # Note (CCB): Our previous method of slug generation did not account for ID verification. By using a list
        # we can update these seats. This should be removed after the courses have been re-migrated.
        if certificate_type == 'verified':
            slugs.append(slug)

        if id_verification_required:
            slug += '-id-verified'
        slugs.append(slug)
        slugs = set(slugs)

        try:
            seat = Product.objects.get(slug__in=slugs)
            logger.info('Retrieved [%s] course seat child product for [%s] from database.', certificate_type,
                        course_id)
        except Product.DoesNotExist:
            seat = Product(slug=slug)
            logger.info('[%s] course seat product for [%s] does not exist. Instantiated a new instance.',
                        certificate_type, course_id)

        seat.course = self
        seat.parent = self.parent_seat_product
        seat.is_discountable = True
        seat.structure = Product.CHILD
        seat.title = self._get_course_seat_name(certificate_type, id_verification_required)
        seat.expires = expires
        seat.attr.certificate_type = certificate_type
        seat.attr.course_key = course_id
        seat.attr.id_verification_required = id_verification_required

        if credit_provider:
            seat.attr.credit_provider = credit_provider

        if credit_hours:
            seat.attr.credit_hours = credit_hours

        seat.save()

        # TODO Expose via setting
        partner = Partner.objects.get(code='edx')
        try:
            stock_record = StockRecord.objects.get(product=seat, partner=partner)
            logger.info('Retrieved [%s] course seat child product stock record for [%s] from database.',
                        certificate_type, course_id)
        except StockRecord.DoesNotExist:
            partner_sku = generate_sku(seat)
            stock_record = StockRecord(product=seat, partner=partner, partner_sku=partner_sku)
            logger.info(
                '[%s] course seat product stock record for [%s] does not exist. Instantiated a new instance.',
                certificate_type, course_id)

        stock_record.price_excl_tax = price

        # TODO Expose via setting
        stock_record.price_currency = 'USD'
        stock_record.save()

        return seat
예제 #16
0
 def test_generate_sku_with_missing_product_class(self):
     """Verify the method raises an exception if the product class is missing."""
     with self.assertRaises(AttributeError):
         generate_sku(Product(), self.partner)
예제 #17
0
 def assert_stock_record_valid(self, stock_record, seat, price):
     """ Verify the given StockRecord is configured correctly. """
     self.assertEqual(stock_record.partner, self.partner)
     self.assertEqual(stock_record.price_excl_tax, price)
     self.assertEqual(stock_record.price_currency, 'USD')
     self.assertEqual(stock_record.partner_sku, generate_sku(seat, self.partner))
예제 #18
0
 def test_generate_sku_with_missing_product_class(self):
     """Verify the method raises an exception if the product class is missing."""
     with self.assertRaises(AttributeError):
         generate_sku(Product(), self.partner)