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
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)
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)
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)
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)
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))
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
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
def latest_version(self): """Gets the most recently created ProductVersion associated with this Product""" return first_or_none(self.ordered_versions)
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