Example #1
0
 def clean_max_global_applications(self):
     # enterprise offers have their own cleanup logic
     if self.priority != OFFER_PRIORITY_ENTERPRISE and self.max_global_applications is not None:
         if not isinstance(self.max_global_applications, six.integer_types) or self.max_global_applications < 1:
             log_message_and_raise_validation_error(
                 'Failed to create ConditionalOffer. max_global_applications field must be a positive number.'
             )
Example #2
0
 def clean_code(self):
     if not self.code:
         log_message_and_raise_validation_error('Failed to create Voucher. Voucher code must be set.')
     if not self.code.isalnum():
         log_message_and_raise_validation_error(
             'Failed to create Voucher. Voucher code must contain only alphanumeric characters.'
         )
Example #3
0
 def clean_code(self):
     if not self.code:
         log_message_and_raise_validation_error('Failed to create Voucher. Voucher code must be set.')
     if not self.code.isalnum():
         log_message_and_raise_validation_error(
             'Failed to create Voucher. Voucher code must contain only alphanumeric characters.'
         )
Example #4
0
    def update_offer_data(self, request_data, vouchers, site):
        """
        Remove all offers from the vouchers and add a new offer
        Arguments:
            request_data (dict): the request parameters sent via api.
            vouchers (list): the vouchers attached to this coupon to update.
            site (Site): the site for this request.
        """
        benefit_value = request_data.get('benefit_value')
        enterprise_customer = request_data.get('enterprise_customer',
                                               {}).get('id', None)
        enterprise_catalog = request_data.get(
            'enterprise_customer_catalog') or None
        max_uses = request_data.get('max_uses')
        email_domains = request_data.get('email_domains')

        # Validate max_uses
        if max_uses is not None:
            if vouchers.first().usage == Voucher.SINGLE_USE:
                log_message_and_raise_validation_error(
                    'Failed to update Coupon. '
                    'max_global_applications field cannot be set for voucher type [{voucher_type}].'
                    .format(voucher_type=Voucher.SINGLE_USE))
            try:
                max_uses = int(max_uses)
                if max_uses < 1:
                    raise ValueError
            except ValueError:
                raise ValidationError(
                    'max_global_applications field must be a positive number.')

        coupon_was_migrated = False
        for voucher in vouchers.all():
            updated_enterprise_offer = update_voucher_with_enterprise_offer(
                offer=voucher.enterprise_offer,
                benefit_value=benefit_value,
                max_uses=max_uses,
                enterprise_customer=enterprise_customer,
                enterprise_catalog=enterprise_catalog,
                email_domains=email_domains,
                site=site,
            )
            updated_orginal_offer = None
            if voucher.original_offer != voucher.enterprise_offer:
                coupon_was_migrated = True
                updated_orginal_offer = update_voucher_offer(
                    offer=voucher.original_offer,
                    benefit_value=benefit_value,
                    max_uses=max_uses,
                    email_domains=email_domains,
                    site=site,
                )
            voucher.offers.clear()
            voucher.offers.add(updated_enterprise_offer)
            if updated_orginal_offer:
                voucher.offers.add(updated_orginal_offer)

        if coupon_was_migrated:
            super(EnterpriseCouponViewSet,
                  self).update_range_data(request_data, vouchers)
Example #5
0
 def clean_max_global_applications(self):
     if self.max_global_applications is not None:
         if self.max_global_applications < 1 or not isinstance(
                 self.max_global_applications, (int, long)):
             log_message_and_raise_validation_error(
                 'Failed to create ConditionalOffer. max_global_applications field must be a positive number.'
             )
Example #6
0
def _create_new_voucher(code, coupon, end_datetime, name, offer,
                        start_datetime, voucher_type):
    """
    Creates a voucher.

    If randomly generated voucher code already exists, new code will be generated and reverified.

    Args:
        code (str): Code associated with vouchers. If not provided, one will be generated.
        coupon (Product): Coupon product associated with voucher.
        end_datetime (datetime): Voucher end date.
        name (str): Voucher name.
        offer (Offer): Offer associated with voucher.
        start_datetime (datetime): Voucher start date.
        voucher_type (str): Voucher usage.

    Returns:
        Voucher
    """
    if offer.benefit.type == Benefit.PERCENTAGE and offer.benefit.value == 100 and code:
        log_message_and_raise_validation_error(
            'Failed to create Voucher. Code may not be set for enrollment coupon.'
        )
    voucher_code = code or _generate_code_string(settings.VOUCHER_CODE_LENGTH)

    if not end_datetime:
        log_message_and_raise_validation_error(
            'Failed to create Voucher. Voucher end datetime field must be set.'
        )
    elif not isinstance(end_datetime, datetime.datetime):
        try:
            end_datetime = dateutil.parser.parse(end_datetime)
        except (AttributeError, ValueError):
            log_message_and_raise_validation_error(
                'Failed to create Voucher. Voucher end datetime value [{date}] is invalid.'
                .format(date=end_datetime))

    if not start_datetime:
        log_message_and_raise_validation_error(
            'Failed to create Voucher. Voucher start datetime field must be set.'
        )
    elif not isinstance(start_datetime, datetime.datetime):
        try:
            start_datetime = dateutil.parser.parse(start_datetime)
        except (AttributeError, ValueError):
            log_message_and_raise_validation_error(
                'Failed to create Voucher. Voucher start datetime [{date}] is invalid.'
                .format(date=start_datetime))

    voucher = Voucher.objects.create(name=name,
                                     code=voucher_code,
                                     usage=voucher_type,
                                     start_datetime=start_datetime,
                                     end_datetime=end_datetime)
    voucher.offers.add(offer)

    coupon_voucher, __ = CouponVouchers.objects.get_or_create(coupon=coupon)
    coupon_voucher.vouchers.add(voucher)

    return voucher
Example #7
0
 def clean_percentage(self):
     if not self.range:
         log_message_and_raise_validation_error(
             'Percentage benefits require a product range')
     if self.value > 100:
         log_message_and_raise_validation_error(
             'Percentage discount cannot be greater than 100')
Example #8
0
 def save(self, *args, **kwargs):
     try:
         if not isinstance(self.attr.note, basestring) and self.attr.note is not None:
             log_message_and_raise_validation_error(
                 'Failed to create Product. Product note value must be of type string'
             )
     except AttributeError:
         pass
     super(Product, self).save(*args, **kwargs)  # pylint: disable=bad-super-call
Example #9
0
    def clean_datetimes(self):
        if not (self.end_datetime and self.start_datetime):
            log_message_and_raise_validation_error(
                'Failed to create Voucher. Voucher start and end datetime fields must be set.'
            )

        if not (isinstance(self.end_datetime, datetime.datetime)
                and isinstance(self.start_datetime, datetime.datetime)):
            log_message_and_raise_validation_error(
                'Failed to create Voucher. Voucher start and end datetime fields must be type datetime.'
            )
Example #10
0
    def clean_datetimes(self):
        if not (self.end_datetime and self.start_datetime):
            log_message_and_raise_validation_error(
                'Failed to create Voucher. Voucher start and end datetime fields must be set.'
            )

        if not (isinstance(self.end_datetime, datetime.datetime) and
                isinstance(self.start_datetime, datetime.datetime)):
            log_message_and_raise_validation_error(
                'Failed to create Voucher. Voucher start and end datetime fields must be type datetime.'
            )
Example #11
0
    def remind(self, request, pk):  # pylint: disable=unused-argument
        """
        Remind users of pending offer assignments by email.
        """
        coupon = self.get_object()
        email_template = request.data.pop('template', None)
        if not email_template:
            log_message_and_raise_validation_error(
                str('Template is required.'))

        if request.data.get('assignments'):
            assignments = request.data.get('assignments')
        else:
            # If no assignment is passed, send reminder to all assignments associated with the coupon.
            vouchers = coupon.attr.coupon_vouchers.vouchers.all()
            code_filter = request.data.get('code_filter')

            if not code_filter:
                raise serializers.ValidationError(
                    'code_filter must be specified')

            if code_filter == VOUCHER_NOT_REDEEMED:
                assignment_usages = self._get_not_redeemed_usages(vouchers)
            elif code_filter == VOUCHER_PARTIAL_REDEEMED:
                assignment_usages = self._get_partial_redeemed_usages(vouchers)
            else:
                raise serializers.ValidationError(
                    'Invalid code_filter specified: {}'.format(code_filter))

            assignments = [{
                'code': assignment['code'],
                'email': assignment['user_email']
            } for assignment in assignment_usages]

        serializer = CouponCodeRemindSerializer(data=assignments,
                                                many=True,
                                                context={
                                                    'coupon': coupon,
                                                    'template': email_template
                                                })
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Example #12
0
    def clean_email_domains(self):

        if self.email_domains:
            if not isinstance(self.email_domains, str):
                log_message_and_raise_validation_error(
                    'Failed to create ConditionalOffer. ConditionalOffer email domains must be of type string.'
                )

            email_domains_array = self.email_domains.split(',')

            if not email_domains_array[-1]:
                log_message_and_raise_validation_error(
                    'Failed to create ConditionalOffer. '
                    'Trailing comma for ConditionalOffer email domains is not allowed.'
                )

            for domain in email_domains_array:
                domain_parts = domain.split('.')
                error_message = 'Failed to create ConditionalOffer. ' \
                                'Email domain [{email_domain}] is invalid.'.format(email_domain=domain)

                # Conditions being tested:
                # - double hyphen not allowed
                # - must contain at least one dot
                # - top level domain must be at least two characters long
                # - hyphens are not allowed in top level domain
                # - numbers are not allowed in top level domain
                if any([
                        '--' in domain,
                        len(domain_parts) < 2,
                        len(domain_parts[-1]) < 2,
                        re.findall(r'[-0-9]', domain_parts[-1])
                ]):
                    log_message_and_raise_validation_error(error_message)

                for domain_part in domain_parts:
                    # - non of the domain levels can start or end with a hyphen before encoding
                    if domain_part.startswith('-') or domain_part.endswith(
                            '-'):
                        log_message_and_raise_validation_error(error_message)

                    # - all encoded domain levels must match given regex expression
                    if not re.match(r'^([a-z0-9-]+)$',
                                    domain_part.encode('idna').decode()):
                        log_message_and_raise_validation_error(error_message)
Example #13
0
    def update_offer_data(self, data, vouchers, coupon_id):
        offer_data = self.create_update_data_dict(data=data, fields=ConditionalOffer.UPDATABLE_OFFER_FIELDS)

        if offer_data:
            if offer_data.get('max_global_applications') is not None:
                if vouchers.first().usage == Voucher.SINGLE_USE:
                    log_message_and_raise_validation_error(
                        'Failed to update Coupon [{coupon_id}]. '
                        'max_global_applications field cannot be set for voucher type [{voucher_type}].'.format(
                            coupon_id=coupon_id,
                            voucher_type=Voucher.SINGLE_USE
                        )
                    )
                try:
                    offer_data['max_global_applications'] = int(offer_data['max_global_applications'])
                    if offer_data['max_global_applications'] < 1:
                        raise ValueError
                except ValueError:
                    raise ValidationError('max_global_applications field must be a positive number.')
            ConditionalOffer.objects.filter(vouchers__in=vouchers.all()).update(**offer_data)
Example #14
0
    def save(self, *args, **kwargs):
        try:
            if not isinstance(self.attr.note,
                              str) and self.attr.note is not None:
                log_message_and_raise_validation_error(
                    'Failed to create Product. Product note value must be of type string'
                )
        except AttributeError:
            pass

        try:
            if self.attr.notify_email is not None:
                validate_email(self.attr.notify_email)
        except ValidationError:
            log_message_and_raise_validation_error(
                'Notification email must be a valid email address.')
        except AttributeError:
            pass

        super(Product, self).save(*args, **kwargs)  # pylint: disable=bad-super-call
Example #15
0
    def remind(self, request, pk):  # pylint: disable=unused-argument
        """
        Remind users of pending offer assignments by email.
        """
        coupon = self.get_object()
        email_template = request.data.pop('template', None)
        if not email_template:
            log_message_and_raise_validation_error(
                str('Template is required.'))
        serializer = CouponCodeRemindSerializer(
            data=request.data.get('assignments'),
            many=True,
            context={
                'coupon': coupon,
                'template': email_template
            })
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_200_OK)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Example #16
0
    def update_offer_data(self, data, vouchers, coupon_id):
        offer_data = self.create_update_data_dict(data=data, fields=ConditionalOffer.UPDATABLE_OFFER_FIELDS)

        if offer_data:
            if offer_data.get('max_global_applications') is not None:
                if vouchers.first().usage == Voucher.SINGLE_USE:
                    log_message_and_raise_validation_error(
                        'Failed to update Coupon [{coupon_id}]. '
                        'max_global_applications field cannot be set for voucher type [{voucher_type}].'.format(
                            coupon_id=coupon_id,
                            voucher_type=Voucher.SINGLE_USE
                        )
                    )
                try:
                    offer_data['max_global_applications'] = int(offer_data['max_global_applications'])
                    if offer_data['max_global_applications'] < 1:
                        raise ValueError
                except ValueError:
                    log_message_and_raise_validation_error(
                        'Failed to update Coupon [{coupon_id}]. '
                        'max_global_applications field must be a positive number.'.format(coupon_id=coupon_id)
                    )
            ConditionalOffer.objects.filter(vouchers__in=vouchers.all()).update(**offer_data)
Example #17
0
    def clean(self):
        """ Validation for model fields. """
        if self.catalog and (self.course_catalog or self.catalog_query or self.course_seat_types):
            log_message_and_raise_validation_error(
                'Failed to create Range. Catalog and dynamic catalog fields may not be set in the same range.'
            )

        error_message = 'Failed to create Range. Either catalog_query or course_catalog must be given but not both ' \
                        'and course_seat_types fields must be set.'

        if self.catalog_query and self.course_catalog:
            log_message_and_raise_validation_error(error_message)
        elif (self.catalog_query or self.course_catalog) and not self.course_seat_types:
            log_message_and_raise_validation_error(error_message)
        elif self.course_seat_types and not (self.catalog_query or self.course_catalog):
            log_message_and_raise_validation_error(error_message)

        if self.course_seat_types:
            validate_credit_seat_type(self.course_seat_types)
Example #18
0
    def clean(self):
        """ Validation for model fields. """
        if self.catalog and (self.catalog_query or self.course_seat_types):
            log_message_and_raise_validation_error(
                'Failed to create Range. Catalog and dynamic catalog fields may not be set in the same range.'
            )

        # Both catalog_query and course_seat_types must be set or empty
        error_message = 'Failed to create Range. Both catalog_query and course_seat_types fields must be set.'
        if self.catalog_query and not self.course_seat_types:
            log_message_and_raise_validation_error(error_message)
        elif self.course_seat_types and not self.catalog_query:
            log_message_and_raise_validation_error(error_message)

        if self.course_seat_types:
            validate_credit_seat_type(self.course_seat_types)
Example #19
0
def validate_credit_seat_type(course_seat_types):
    if not isinstance(course_seat_types, basestring):
        log_message_and_raise_validation_error('Failed to create Range. Credit seat types must be of type string.')

    course_seat_types_list = course_seat_types.split(',')

    if len(course_seat_types_list) > 1 and 'credit' in course_seat_types_list:
        log_message_and_raise_validation_error(
            'Failed to create Range. Credit seat type cannot be paired with other seat types.'
        )

    if not set(course_seat_types_list).issubset(set(Range.ALLOWED_SEAT_TYPES)):
        log_message_and_raise_validation_error(
            'Failed to create Range. Not allowed course seat types {}. '
            'Allowed values for course seat types are {}.'.format(course_seat_types_list, Range.ALLOWED_SEAT_TYPES)
        )
Example #20
0
    def clean(self):
        """ Validation for model fields. """
        if self.catalog and (self.catalog_query or self.course_seat_types):
            log_message_and_raise_validation_error(
                'Failed to create Range. Catalog and dynamic catalog fields may not be set in the same range.'
            )

        # Both catalog_query and course_seat_types must be set or empty
        error_message = 'Failed to create Range. Both catalog_query and course_seat_types fields must be set.'
        if self.catalog_query and not self.course_seat_types:
            log_message_and_raise_validation_error(error_message)
        elif self.course_seat_types and not self.catalog_query:
            log_message_and_raise_validation_error(error_message)

        if self.course_seat_types:
            validate_credit_seat_type(self.course_seat_types)
Example #21
0
def validate_credit_seat_type(course_seat_types):
    if not isinstance(course_seat_types, basestring):
        log_message_and_raise_validation_error('Failed to create Range. Credit seat types must be of type string.')

    course_seat_types_list = course_seat_types.split(',')

    if len(course_seat_types_list) > 1 and 'credit' in course_seat_types_list:
        log_message_and_raise_validation_error(
            'Failed to create Range. Credit seat type cannot be paired with other seat types.'
        )

    if not set(course_seat_types_list).issubset(set(Range.ALLOWED_SEAT_TYPES)):
        log_message_and_raise_validation_error(
            'Failed to create Range. Not allowed course seat types {}. '
            'Allowed values for course seat types are {}.'.format(course_seat_types_list, Range.ALLOWED_SEAT_TYPES)
        )
Example #22
0
def create_vouchers(
        benefit_type,
        benefit_value,
        catalog,
        coupon,
        end_datetime,
        enterprise_customer,
        name,
        quantity,
        start_datetime,
        voucher_type,
        code=None,
        max_uses=None,
        _range=None,
        catalog_query=None,
        course_seat_types=None,
        email_domains=None,
        course_catalog=None,
        program_uuid=None,
        site=None,
):
    """
    Create vouchers.

    Arguments:
        benefit_type (str): Type of benefit associated with vouchers.
        benefit_value (Decimal): Value of benefit associated with vouchers.
        catalog (Catalog): Catalog associated with range of products
                           to which a voucher can be applied to.
        catalog_query (str): ElasticSearch query used by dynamic coupons. Defaults to None.
        code (str): Code associated with vouchers. Defaults to None.
        coupon (Coupon): Coupon entity associated with vouchers.
        course_catalog (int): Course catalog id from Discovery Service. Defaults to None.
        course_seat_types (str): Comma-separated list of course seat types.
        email_domains (str): List of email domains to restrict coupons. Defaults to None.
        end_datetime (datetime): End date for voucher offer.
        enterprise_customer (str): UUID of an EnterpriseCustomer attached to this voucher
        max_uses (int): Number of Voucher max uses. Defaults to None.
        name (str): Voucher name.
        quantity (int): Number of vouchers to be created.
        start_datetime (datetime): Start date for voucher offer.
        voucher_type (str): Type of voucher.
        _range (Range): Product range. Defaults to None.
        program_uuid (str): Program UUID. Defaults to None.
        site (site): Site for which the Coupon is created. Defaults to None.

    Returns:
        List[Voucher]
    """
    logger.info("Creating [%d] vouchers product [%s]", quantity, coupon.id)
    vouchers = []
    offers = []

    # Maximum number of uses can be set for each voucher type and disturb
    # the predefined behaviours of the different voucher types. Therefor
    # here we enforce that the max_uses variable can't be used for SINGLE_USE
    # voucher types.
    if max_uses is not None:
        if voucher_type == Voucher.SINGLE_USE:
            log_message_and_raise_validation_error(
                'Failed to create Voucher. max_uses field cannot be set for voucher type [{voucher_type}].'.format(
                    voucher_type=Voucher.SINGLE_USE
                )
            )
        try:
            max_uses = int(max_uses)
        except ValueError:
            raise log_message_and_raise_validation_error('Failed to create Voucher. max_uses field must be a number.')

    if _range:
        # Enrollment codes use a custom range.
        logger.info("Creating [%d] enrollment code vouchers", quantity)
        product_range = _range
    else:
        logger.info("Creating [%d] vouchers for coupon [%s]", quantity, coupon.id)
        range_name = (_('Range for coupon [{coupon_id}]').format(coupon_id=coupon.id))
        # make sure course catalog is None if its empty
        course_catalog = course_catalog if course_catalog else None
        # make sure enterprise_customer is None if it's empty
        enterprise_customer = enterprise_customer or None
        # we do not need a range if this is for a Program
        if program_uuid:
            product_range = None
        else:
            product_range, __ = Range.objects.get_or_create(
                name=range_name,
                catalog=catalog,
                catalog_query=catalog_query,
                course_catalog=course_catalog,
                course_seat_types=course_seat_types,
                enterprise_customer=enterprise_customer,
            )

    # In case of more than 1 multi-usage coupon, each voucher needs to have an individual
    # offer because the usage is tied to the offer so that a usage on one voucher would
    # mean all vouchers will have their usage decreased by one, hence each voucher needs
    # its own offer to keep track of its own usages without interfering with others.
    multi_offer = True if (
        voucher_type == Voucher.MULTI_USE or voucher_type == Voucher.ONCE_PER_CUSTOMER
    ) else False
    num_of_offers = quantity if multi_offer else 1
    for num in range(num_of_offers):
        offer = _get_or_create_offer(
            product_range=product_range,
            benefit_type=benefit_type,
            benefit_value=benefit_value,
            max_uses=max_uses,
            coupon_id=coupon.id,
            offer_number=num,
            email_domains=email_domains,
            program_uuid=program_uuid,
            site=site
        )
        offers.append(offer)

    for i in range(quantity):
        voucher = _create_new_voucher(
            end_datetime=end_datetime,
            offer=offers[i] if multi_offer else offers[0],
            start_datetime=start_datetime,
            voucher_type=voucher_type,
            code=code,
            name=name
        )
        vouchers.append(voucher)

    return vouchers
Example #23
0
 def clean_value(self):
     if self.value < 0:
         log_message_and_raise_validation_error(
             'Failed to create Benefit. Benefit value may not be a negative number.'
         )
Example #24
0
 def save(self, *args, **kwargs):  # pylint: disable=arguments-differ
     if not self.name:
         log_message_and_raise_validation_error(
             'Failed to create BusinessClient. BusinessClient name may not be empty.'
         )
     super(BusinessClient, self).save(*args, **kwargs)
Example #25
0
    def update_offer_data(self, request_data, vouchers, site):
        """
        Remove all offers from the vouchers and add a new offer
        Arguments:
            coupon (Product): Coupon product associated with vouchers
            vouchers (ManyRelatedManager): Vouchers associated with the coupon to be updated
            site (Site): The Site associated with this offer
            benefit_value (Decimal): Benefit value associated with a new offer
            program_uuid (str): Program UUID
            enterprise_customer (str): Enterprise Customer UUID
            enterprise_catalog (str): Enterprise Catalog UUID
        """
        program_uuid = request_data.get('program_uuid')
        benefit_value = request_data.get('benefit_value')
        enterprise_customer_data = request_data.get('enterprise_customer')
        enterprise_customer = enterprise_customer_data.get('id', None) if enterprise_customer_data else None
        enterprise_catalog = request_data.get('enterprise_customer_catalog') or None
        max_uses = request_data.get('max_uses')
        email_domains = request_data.get('email_domains')

        # Validate max_uses
        if max_uses is not None:
            if vouchers.first().usage == Voucher.SINGLE_USE:
                log_message_and_raise_validation_error(
                    'Failed to update Coupon. '
                    'max_global_applications field cannot be set for voucher type [{voucher_type}].'.format(
                        voucher_type=Voucher.SINGLE_USE
                    ))
            try:
                max_uses = int(max_uses)
                if max_uses < 1:
                    raise ValueError
            except ValueError:
                raise ValidationError('max_global_applications field must be a positive number.')

        for voucher in vouchers.all():
            updated_original_offer = update_voucher_offer(
                offer=voucher.original_offer,
                benefit_value=benefit_value,
                max_uses=max_uses,
                program_uuid=program_uuid,
                email_domains=email_domains,
                site=site,
            )
            updated_enterprise_offer = None
            if voucher.enterprise_offer:
                updated_enterprise_offer = update_voucher_with_enterprise_offer(
                    offer=voucher.enterprise_offer,
                    benefit_value=benefit_value,
                    max_uses=max_uses,
                    enterprise_customer=enterprise_customer,
                    enterprise_catalog=enterprise_catalog,
                    email_domains=email_domains,
                    site=site,
                )
            elif enterprise_customer:
                # If we are performing an update on an existing enterprise coupon,
                # we need to ensure the enterprise offer is created if it didn't already exist.
                updated_enterprise_offer = get_or_create_enterprise_offer(
                    benefit_value=benefit_value or voucher.original_offer.benefit.value,
                    benefit_type=voucher.original_offer.benefit.type,
                    enterprise_customer=enterprise_customer,
                    enterprise_customer_catalog=enterprise_catalog,
                    offer_name=voucher.original_offer.name + " ENT Offer",
                    max_uses=max_uses or voucher.original_offer.max_global_applications,
                    email_domains=email_domains or voucher.original_offer.email_domains,
                    site=site or voucher.original_offer.site,
                )
            voucher.offers.clear()
            voucher.offers.add(updated_original_offer)
            if updated_enterprise_offer:
                voucher.offers.add(updated_enterprise_offer)
Example #26
0
def _get_or_create_offer(product_range,
                         benefit_type,
                         benefit_value,
                         offer_name,
                         max_uses,
                         site,
                         email_domains=None,
                         program_uuid=None):
    """
    Return an offer for a catalog with condition and benefit.

    If offer doesn't exist, new offer will be created and associated with
    provided Offer condition and benefit.

    Args:
        product_range (Range): Range of products associated with condition
        benefit_type (str): Type of benefit associated with the offer
        benefit_value (Decimal): Value of benefit associated with the offer
    Kwargs:
        coupon_id (int): ID of the coupon
        max_uses (int): number of maximum global application number an offer can have
        offer_number (int): number of the consecutive offer - used in case of a multiple
                            multi-use coupon
        email_domains (str): a comma-separated string of email domains allowed to apply
                            this offer
        program_uuid (str): the Program UUID
        site (site): Site for which the Coupon is created. Defaults to None.

    Returns:
        Offer
    """

    if program_uuid:
        try:
            offer_condition = ProgramCourseRunSeatsCondition.objects.get(
                program_uuid=program_uuid)
        except ProgramCourseRunSeatsCondition.DoesNotExist:
            offer_condition = create_condition(ProgramCourseRunSeatsCondition,
                                               program_uuid=program_uuid)
    else:
        offer_condition, __ = Condition.objects.get_or_create(
            proxy_class=None,
            range=product_range,
            type=Condition.COUNT,
            value=1,
        )
    try:
        if program_uuid:
            proxy_class = class_path(BENEFIT_MAP[benefit_type])
            offer_benefit = Benefit.objects.filter(
                proxy_class=proxy_class, value=benefit_value).first()

            if not offer_benefit:
                offer_benefit = Benefit()
                offer_benefit.proxy_class = proxy_class
                offer_benefit.value = benefit_value
                offer_benefit.save()

            offer_name = "{}-{}".format(offer_name, offer_benefit.name)
        else:
            offer_benefit, __ = Benefit.objects.get_or_create(
                range=product_range,
                type=benefit_type,
                value=Decimal(benefit_value),
                max_affected_items=1,
            )

    except (
            TypeError, DecimalException
    ):  # If the benefit_value parameter is not sent TypeError will be raised
        log_message_and_raise_validation_error(
            'Failed to create Benefit. Benefit value must be a positive number or 0.'
        )

    offer_kwargs = {
        'offer_type': ConditionalOffer.VOUCHER,
        'condition': offer_condition,
        'benefit': offer_benefit,
        'max_global_applications':
        int(max_uses) if max_uses is not None else None,
        'email_domains': email_domains,
        'site': site,
        'partner': site.siteconfiguration.partner if site else None,
        'priority': OFFER_PRIORITY_VOUCHER,
    }
    offer, __ = ConditionalOffer.objects.update_or_create(
        name=offer_name, defaults=offer_kwargs)

    return offer
Example #27
0
def validate_voucher_fields(max_uses, voucher_type, benefit_type,
                            benefit_value, code, end_datetime, start_datetime):
    # Maximum number of uses can be set for each voucher type and disturb
    # the predefined behaviours of the different voucher types. Therefor
    # here we enforce that the max_uses variable can't be used for SINGLE_USE
    # voucher types.
    if max_uses is not None:
        if voucher_type == Voucher.SINGLE_USE:
            log_message_and_raise_validation_error(
                'Failed to create Voucher. max_uses field cannot be set for voucher type [{voucher_type}].'
                .format(voucher_type=Voucher.SINGLE_USE))
        try:
            int(max_uses)
        except ValueError:
            raise log_message_and_raise_validation_error(
                'Failed to create Voucher. max_uses field must be a number.')

    if benefit_type == Benefit.PERCENTAGE and benefit_value == 100 and code:
        log_message_and_raise_validation_error(
            'Failed to create Voucher. Code may not be set for enrollment coupon.'
        )

    if not end_datetime:
        log_message_and_raise_validation_error(
            'Failed to create Voucher. Voucher end datetime field must be set.'
        )
    elif not isinstance(end_datetime, datetime.datetime):
        try:
            dateutil.parser.parse(end_datetime)
        except (AttributeError, ValueError, TypeError):
            log_message_and_raise_validation_error(
                'Failed to create Voucher. Voucher end datetime value [{date}] is invalid.'
                .format(date=end_datetime))

    if not start_datetime:
        log_message_and_raise_validation_error(
            'Failed to create Voucher. Voucher start datetime field must be set.'
        )
    elif not isinstance(start_datetime, datetime.datetime):
        try:
            dateutil.parser.parse(start_datetime)
        except (AttributeError, ValueError, TypeError):
            log_message_and_raise_validation_error(
                'Failed to create Voucher. Voucher start datetime [{date}] is invalid.'
                .format(date=start_datetime))
Example #28
0
 def clean_type(self):
     if self.type not in self.VALID_BENEFIT_TYPES:
         log_message_and_raise_validation_error(
             'Failed to create Benefit. Unrecognised benefit type [{type}]'.
             format(type=self.type))
Example #29
0
 def clean_type(self):
     if self.type not in self.VALID_BENEFIT_TYPES:
         log_message_and_raise_validation_error(
             'Failed to create Benefit. Unrecognised benefit type [{type}]'.format(type=self.type)
         )
Example #30
0
 def save(self, *args, **kwargs):
     if not self.name:
         log_message_and_raise_validation_error(
             'Failed to create BusinessClient. BusinessClient name may not be empty.'
         )
     super(BusinessClient, self).save(*args, **kwargs)
Example #31
0
 def clean_value(self):
     if self.value < 0:
         log_message_and_raise_validation_error(
             'Failed to create Benefit. Benefit value may not be a negative number.'
         )
Example #32
0
def _get_or_create_offer(product_range,
                         benefit_type,
                         benefit_value,
                         coupon_id=None,
                         max_uses=None,
                         offer_number=None,
                         email_domains=None):
    """
    Return an offer for a catalog with condition and benefit.

    If offer doesn't exist, new offer will be created and associated with
    provided Offer condition and benefit.

    Args:
        product_range (Range): Range of products associated with condition
        benefit_type (str): Type of benefit associated with the offer
        benefit_value (Decimal): Value of benefit associated with the offer
    Kwargs:
        coupon_id (int): ID of the coupon
        max_uses (int): number of maximum global application number an offer can have
        offer_number (int): number of the consecutive offer - used in case of a multiple
                            multi-use coupon
        email_domains (str): a comma-separated string of email domains allowed to apply
                            this offer

    Returns:
        Offer
    """
    offer_condition, __ = Condition.objects.get_or_create(
        range=product_range,
        type=Condition.COUNT,
        value=1,
    )
    try:
        offer_benefit, __ = Benefit.objects.get_or_create(
            range=product_range,
            type=benefit_type,
            value=Decimal(benefit_value),
            max_affected_items=1,
        )
    except (
            TypeError, DecimalException
    ):  # If the benefit_value parameter is not sent TypeError will be raised
        log_message_and_raise_validation_error(
            'Failed to create Benefit. Benefit value must be a positive number or 0.'
        )

    offer_name = "Coupon [{}]-{}-{}".format(coupon_id, offer_benefit.type,
                                            offer_benefit.value)
    if offer_number:
        offer_name = "{} [{}]".format(offer_name, offer_number)

    offer, __ = ConditionalOffer.objects.get_or_create(
        name=offer_name,
        offer_type=ConditionalOffer.VOUCHER,
        condition=offer_condition,
        benefit=offer_benefit,
        max_global_applications=max_uses,
        email_domains=email_domains)

    return offer
Example #33
0
    def clean_email_domains(self):
        if self.email_domains == '':
            log_message_and_raise_validation_error(
                'Failed to create ConditionalOffer. ConditionalOffer email domains may not be an empty string.'
            )

        if self.email_domains:
            if not isinstance(self.email_domains, basestring):
                log_message_and_raise_validation_error(
                    'Failed to create ConditionalOffer. ConditionalOffer email domains must be of type string.'
                )

            email_domains_array = self.email_domains.split(',')

            if not email_domains_array[-1]:
                log_message_and_raise_validation_error(
                    'Failed to create ConditionalOffer. '
                    'Trailing comma for ConditionalOffer email domains is not allowed.'
                )

            for domain in email_domains_array:
                domain_parts = domain.split('.')
                error_message = 'Failed to create ConditionalOffer. ' \
                                'Email domain [{email_domain}] is invalid.'.format(email_domain=domain)

                # Conditions being tested:
                # - double hyphen not allowed
                # - must contain at least one dot
                # - top level domain must be at least two characters long
                # - hyphens are not allowed in top level domain
                # - numbers are not allowed in top level domain
                if any(['--' in domain,
                        len(domain_parts) < 2,
                        len(domain_parts[-1]) < 2,
                        re.findall(r'[-0-9]', domain_parts[-1])]):
                    log_message_and_raise_validation_error(error_message)

                for domain_part in domain_parts:
                    # - non of the domain levels can start or end with a hyphen before encoding
                    if domain_part.startswith('-') or domain_part.endswith('-'):
                        log_message_and_raise_validation_error(error_message)

                    # - all encoded domain levels must match given regex expression
                    if not re.match(r'^([a-z0-9-]+)$', domain_part.encode('idna')):
                        log_message_and_raise_validation_error(error_message)
Example #34
0
 def clean_max_global_applications(self):
     if self.max_global_applications is not None:
         if self.max_global_applications < 1 or not isinstance(self.max_global_applications, (int, long)):
             log_message_and_raise_validation_error(
                 'Failed to create ConditionalOffer. max_global_applications field must be a positive number.'
             )