Exemplo n.º 1
0
def test_first_or_none():
    """
    Assert that first_or_none returns the first item in an iterable or None
    """
    assert first_or_none([]) is None
    assert first_or_none(set()) is None
    assert first_or_none([1, 2, 3]) == 1
    assert first_or_none(range(1, 5)) == 1
Exemplo n.º 2
0
    def iter_seed_data(self, raw_data):
        """
        Iterate through raw seed data and yields the specification for models that will be created/updated/deleted
        """
        for raw_program_data in raw_data["programs"]:
            yield SeedDataSpec(model_cls=Program,
                               data=raw_program_data,
                               parent=None)

        for raw_course_data in raw_data["courses"]:
            program_title = raw_course_data.get("program")
            program_id = (first_or_none(
                Program.objects.filter(
                    title=self.seed_prefixed(program_title)).values_list(
                        "id", flat=True)) if program_title else None)
            course_runs_data = raw_course_data.get("course_runs", [])
            course_spec = SeedDataSpec(
                model_cls=Course,
                data={
                    # The deserializer (or Django) will think "course_runs" has CourseRun objects,
                    # so it has to be excluded
                    **dict_without_keys(raw_course_data, "course_runs"),
                    "program":
                    program_id,
                },
                parent=None,
            )
            yield course_spec

            course_title = raw_course_data["title"]
            course_id = first_or_none(
                Course.objects.filter(
                    title=self.seed_prefixed(course_title)).values_list(
                        "id", flat=True))
            for raw_course_run_data in course_runs_data:
                yield SeedDataSpec(
                    model_cls=CourseRun,
                    data={
                        **raw_course_run_data, "course": course_id
                    },
                    parent=course_spec,
                )

        for resource_page_data in raw_data["resource_pages"]:
            yield SeedDataSpec(model_cls=ResourcePage,
                               data=resource_page_data,
                               parent=None)

        for raw_company_data in raw_data["companies"]:
            yield SeedDataSpec(model_cls=Company,
                               data=raw_company_data,
                               parent=None)

        for raw_coupon_data in raw_data["coupons"]:
            yield SeedDataSpec(model_cls=CouponPayment,
                               data=raw_coupon_data,
                               parent=None)
Exemplo n.º 3
0
def defer_enrollment(
    user,
    from_courseware_id,
    to_courseware_id,
    keep_failed_enrollments=False,
    force=False,
):
    """
    Deactivates a user's existing enrollment in one course run and enrolls the user in another.

    Args:
        user (User): The enrolled user
        from_courseware_id (str): The courseware_id value of the currently enrolled CourseRun
        to_courseware_id (str): The courseware_id value of the desired CourseRun
        keep_failed_enrollments: (boolean): If True, keeps the local enrollment record
            in the database even if the enrollment fails in edX.
        force (bool): If True, the deferral will be completed even if the current enrollment is inactive
            or the desired enrollment is in a different course

    Returns:
        (CourseRunEnrollment, CourseRunEnrollment): The deactivated enrollment paired with the
            new enrollment that was the target of the deferral
    """
    from_enrollment = CourseRunEnrollment.all_objects.get(
        user=user, run__courseware_id=from_courseware_id)
    if not force and not from_enrollment.active:
        raise ValidationError(
            "Cannot defer from inactive enrollment (id: {}, run: {}, user: {}). "
            "Set force=True to defer anyway.".format(
                from_enrollment.id, from_enrollment.run.courseware_id,
                user.email))
    to_run = CourseRun.objects.get(courseware_id=to_courseware_id)
    if from_enrollment.run == to_run:
        raise ValidationError(
            "Cannot defer to the same course run (run: {})".format(
                to_run.courseware_id))
    if not to_run.is_not_beyond_enrollment:
        raise ValidationError(
            "Cannot defer to a course run that is outside of its enrollment period (run: {})."
            .format(to_run.courseware_id))
    if not force and from_enrollment.run.course != to_run.course:
        raise ValidationError(
            "Cannot defer to a course run of a different course ('{}' -> '{}'). "
            "Set force=True to defer anyway.".format(
                from_enrollment.run.course.title, to_run.course.title))
    to_enrollments, _ = create_run_enrollments(
        user,
        [to_run],
        order=from_enrollment.order,
        company=from_enrollment.company,
        keep_failed_enrollments=keep_failed_enrollments,
    )
    from_enrollment = deactivate_run_enrollment(
        from_enrollment,
        ENROLL_CHANGE_STATUS_DEFERRED,
        keep_failed_enrollments=keep_failed_enrollments,
    )
    return from_enrollment, first_or_none(to_enrollments)
Exemplo n.º 4
0
    def get(self, request, *args, **kwargs):  # pylint: disable=unused-argument
        """
        Handles GET requests. Response data is of this form:

        {
            "coupon_payments": [
                {
                    <serialized CouponPayment>,
                    "products": [
                        <basic serialized data for a Product that the CouponPayment applies to>,
                        ...
                    ]
                },
                ...
            ],
            "product_map": {
                "courserun": {
                    "<Product.id>": {<serialized CourseRun>},
                    ...
                },
                "program": {
                    "<Product.id>": {<serialized Program>},
                    ...
                }
            }
        """
        product_set = set()
        serialized = {"coupon_payments": []}
        for coupon_payment, products in get_full_price_coupon_product_set():
            for product in products:
                if product not in product_set:
                    product_set.add(product)
            serialized["coupon_payments"].append(
                {
                    **CurrentCouponPaymentSerializer(
                        coupon_payment,
                        context={
                            "latest_version": first_or_none(
                                coupon_payment.ordered_versions
                            )
                        },
                    ).data,
                    "products": ProductSerializer(products, many=True).data,
                }
            )
        serialized["product_map"] = defaultdict(dict)
        for product in product_set:
            product_object = product.content_object
            serialized["product_map"][product.content_type.model][str(product.id)] = (
                BaseCourseRunSerializer(product_object).data
                if isinstance(product_object, CourseRun)
                else BaseProgramSerializer(product_object).data
            )

        return Response(status=status.HTTP_200_OK, data=serialized)
Exemplo n.º 5
0
def enroll_user_in_order_items(order):
    """
    Enroll the user in the CourseRuns associated with their Order, and create local records of their
    enrollments.

    Args:
        order (Order): An order
    """
    order_line = Line.objects.prefetch_related("line_selections__run").get(
        order=order)
    runs = [
        line_selection.run
        for line_selection in order_line.line_selections.all()
    ]
    programs = get_order_programs(order)
    company = get_company_affiliation(order)

    if programs and not runs:
        log.error(
            "An order is being completed for a program, but does not have any course run selections. "
            "(Order: %d, purchaser: '%s', program(s): %s)",
            order.id,
            order.purchaser.email,
            [program.readable_id for program in programs],
        )

    successful_run_enrollments = []
    if runs:
        successful_run_enrollments, _ = create_run_enrollments(
            order.purchaser,
            runs,
            order=order,
            company=company,
            keep_failed_enrollments=True,
        )

    voucher = (order.purchaser.vouchers.filter(product_id__in=order.lines.all(
    ).values_list("product_version__product__id", flat=True)).order_by(
        "uploaded").last())
    voucher_target = None
    if (voucher and voucher.is_redeemed() and voucher.product is not None
            and voucher.enrollment is None):
        voucher_target = voucher.product.content_object
    voucher_enrollment = first_or_none(
        (enrollment for enrollment in successful_run_enrollments
         if enrollment.run == voucher_target))
    if voucher_enrollment is not None:
        voucher.enrollment = voucher_enrollment
        voucher.save()

    if programs:
        create_program_enrollments(order.purchaser,
                                   programs,
                                   order=order,
                                   company=company)
Exemplo n.º 6
0
def get_affiliate_id_from_code(affiliate_code):
    """
    Helper method that fetches the Affiliate id from the database that matches the affiliate code

    Args:
        affiliate_code (str): The affiliate code

    Returns:
        Optional[Affiliate]: The id of the Affiliate that matches the given code (if it exists)
    """
    return first_or_none(
        Affiliate.objects.filter(code=affiliate_code).values_list("id",
                                                                  flat=True))
Exemplo n.º 7
0
def _validate_coupon_selection(basket, product):
    """
    Verifies that the any coupons applied to the basket are valid, and returns the coupon version if a
    valid coupon code was applied.

    Args:
        basket (Basket): The basket being validated
        product (Product): The product being purchased

    Returns:
        Optional(CouponVersion): The coupon version associated with the applied coupon code (or None if no code was
            applied to the basket)
    """
    coupon_selections = basket.couponselection_set
    if coupon_selections.count() > 1:
        log.error(
            "User %s is checking out with multiple coupon selections. There should be one or zero.",
            basket.user.email,
        )
        raise ValidationError({
            "coupons":
            "Something went wrong with your coupon. Please clear it and try again."
        })
    coupon_selection = first_or_none(coupon_selections.all())
    if coupon_selection is None:
        coupon_version = None
    else:
        valid_product_coupon_versions = get_valid_coupon_versions(
            product=product,
            user=basket.user,
            code=coupon_selection.coupon.coupon_code)
        coupon_version = first_or_none(valid_product_coupon_versions)
        if coupon_version is None:
            raise ValidationError(
                {"coupons": "Coupon is not valid for product."})
    return coupon_version
Exemplo n.º 8
0
def fetch_users(filter_values, ignore_case=True):
    """
    Attempts to fetch a set of users based on several properties. The property being searched
    (i.e.: id, email, or username) is assumed to be the same for all of the given values, so the
    property type is determined for the first element, then used for all of the values provided.

    Args:
        filter_values (iterable of Union[str, int]): The ids, emails, or usernames of the target Users
        ignore_case (bool): If True, the User query will be case-insensitive
    Returns:
        User queryset or None: Users that match the given properties
    """

    first_user_property = first_or_none(filter_values)
    if not first_user_property:
        return None
    filter_field = _determine_filter_field(first_user_property)
    is_case_insensitive_searchable = _is_case_insensitive_searchable(
        filter_field)

    unique_filter_values = set(
        unique_ignore_case(filter_values) if is_case_insensitive_searchable
        and ignore_case else unique(filter_values))
    if len(filter_values) > len(unique_filter_values):
        raise ValidationError("Duplicate values provided ({})".format(
            set(filter_values).intersection(unique_filter_values)))

    if is_case_insensitive_searchable and ignore_case:
        query = reduce(
            operator.or_,
            (Q(**{"{}__iexact".format(filter_field): filter_value})
             for filter_value in filter_values),
        )
        user_qset = User.objects.filter(query)
    else:
        user_qset = User.objects.filter(
            **{"{}__in".format(filter_field): filter_values})
    if not user_qset.count() == len(filter_values):
        valid_values = user_qset.values_list(filter_field, flat=True)
        invalid_values = set(filter_values) - set(valid_values)
        raise User.DoesNotExist(
            "Could not find Users with these '{}' values ({}): {}".format(
                filter_field,
                "case-insensitive" if ignore_case else "case-sensitive",
                sorted(list(invalid_values)),
            ))
    return user_qset
Exemplo n.º 9
0
 def latest_version(self):
     """Gets the most recently created ProductVersion associated with this Product"""
     return first_or_none(self.ordered_versions)
Exemplo n.º 10
0
def get_product_from_text_id(text_id):
    """
    Fetches a product from a text id that references a Program or CourseRun. If the text id is for a
    Program, and that id has a program run suffix (ex: "+R1"), an associated ProgramRun is also returned.

    Args:
        text_id (str): A text id for a Program/CourseRun

    Returns:
        (Product, Program or CourseRun, ProgramRun): A tuple containing the Product for the CourseRun/Program,
            the Program/CourseRun associated with the text id, and a matching ProgramRun if the text id
            indicated one
    """
    program_run_id_match = re.match(PROGRAM_RUN_ID_PATTERN, text_id)
    # This text id matches the pattern of a program text id with a program run attached
    if program_run_id_match:
        match_dict = program_run_id_match.groupdict()
        potential_prog_run_id = match_dict["run_tag"]
        potential_text_id_base = match_dict["text_id_base"]
        # A Program's own text id may end with something that looks like a ProgramRun suffix, but has
        # no associated ProgramRun (ex: program.readable_id == "program-v1:my+program+R1"). This query looks
        # for a Program with a ProgramRun that matches the suffix, or one that matches the full given text id
        # without a ProgramRun. The version with a matching ProgramRun is preferred.
        program = (Program.objects.filter(
            Q(
                readable_id=potential_text_id_base,
                programruns__run_tag=potential_prog_run_id,
            )
            | Q(readable_id=text_id)).order_by(
                "-programruns__run_tag").prefetch_related(
                    Prefetch(
                        "programruns",
                        queryset=ProgramRun.objects.filter(
                            run_tag=potential_prog_run_id),
                        to_attr="matching_program_runs",
                    )).prefetch_related("products").first())
        if not program:
            raise Program.DoesNotExist(
                f"Could not find Program with readable_id={text_id} "
                "or readable_id={potential_text_id_base} with program run {potential_prog_run_id}"
            )
        program_run = first_or_none(program.matching_program_runs)
        product = first_or_none(program.products.all())
        if not product:
            raise Product.DoesNotExist(f"Product for {program} does not exist")
        return product, program, program_run
    # This is a "normal" text id that should match a CourseRun/Program
    else:
        if is_program_text_id(text_id):
            content_object_model = Program
            content_object_filter = dict(readable_id=text_id)
        else:
            content_object_model = CourseRun
            content_object_filter = dict(courseware_id=text_id)
        content_object = (content_object_model.objects.filter(
            **content_object_filter).prefetch_related("products").first())
        if not content_object:
            raise content_object_model.DoesNotExist(
                f"{content_object_model._meta.model} matching filter {content_object_filter} does not exist"
            )
        product = first_or_none(content_object.products.all())
        if not product:
            raise Product.DoesNotExist(
                f"Product for {content_object} does not exist")
        return product, content_object, None