def test_enabled_for_course( self, before_enabled, ): config = ContentTypeGatingConfig.objects.create( enabled=True, course=self.course_overview, enabled_as_of=timezone.now(), ) # Tweak the datetime to check for course enablement so it is either # before or after when the configuration was enabled if before_enabled: target_datetime = config.enabled_as_of - timedelta(days=1) else: target_datetime = config.enabled_as_of + timedelta(days=1) course_key = self.course_overview.id self.assertEqual( not before_enabled, ContentTypeGatingConfig.enabled_for_course( course_key=course_key, target_datetime=target_datetime, ) )
def test_enabled_for_course( self, before_enabled, ): config = ContentTypeGatingConfig.objects.create( enabled=True, course=self.course_overview, enabled_as_of=timezone.now(), ) # Tweak the datetime to check for course enablement so it is either # before or after when the configuration was enabled if before_enabled: target_datetime = config.enabled_as_of - timedelta(days=1) else: target_datetime = config.enabled_as_of + timedelta(days=1) course_key = self.course_overview.id self.assertEqual( not before_enabled, ContentTypeGatingConfig.enabled_for_course( course_key=course_key, target_datetime=target_datetime, ))
def create_content_gating_partition(course): """ Create and return the Content Gating user partition. """ if not ContentTypeGatingConfig.enabled_for_course(course_key=course.id): return None try: content_gate_scheme = UserPartition.get_scheme("content_type_gate") except UserPartitionError: LOG.warning("No 'content_type_gate' scheme registered, ContentTypeGatingPartitionScheme will not be created.") return None used_ids = set(p.id for p in course.user_partitions) if CONTENT_GATING_PARTITION_ID in used_ids: # It's possible for course authors to add arbitrary partitions via XML import. If they do, and create a # partition with id 51, it will collide with the Content Gating Partition. We'll catch that here, and # then fix the course content as needed (or get the course team to). LOG.warning( "Can't add 'content_type_gate' partition, as ID {id} is assigned to {partition} in course {course}.".format( id=CONTENT_GATING_PARTITION_ID, partition=_get_partition_from_id(course.user_partitions, CONTENT_GATING_PARTITION_ID).name, course=unicode(course.id) ) ) return None partition = content_gate_scheme.create_user_partition( id=CONTENT_GATING_PARTITION_ID, name=_(u"Feature-based Enrollments"), description=_(u"Partition for segmenting users by access to gated content types"), parameters={"course_id": unicode(course.id)} ) return partition
def _get_course_duration_info(self, course_key): """ Fetch course duration information from database """ try: key = CourseKey.from_string(course_key) course = CourseOverview.objects.values('display_name').get(id=key) duration_config = CourseDurationLimitConfig.current(course_key=key) gating_config = ContentTypeGatingConfig.current(course_key=key) duration_enabled = CourseDurationLimitConfig.enabled_for_course(course_key=key) gating_enabled = ContentTypeGatingConfig.enabled_for_course(course_key=key) gating_dict = { 'enabled': gating_enabled, 'enabled_as_of': str(gating_config.enabled_as_of) if gating_config.enabled_as_of else 'N/A', 'reason': gating_config.provenances['enabled'].value } duration_dict = { 'enabled': duration_enabled, 'enabled_as_of': str(duration_config.enabled_as_of) if duration_config.enabled_as_of else 'N/A', 'reason': duration_config.provenances['enabled'].value } return { 'course_id': course_key, 'course_name': course.get('display_name'), 'gating_config': gating_dict, 'duration_config': duration_dict, } except (ObjectDoesNotExist, InvalidKeyError): return {}
def create_content_gating_partition(course): """ Create and return the Content Gating user partition. """ enabled_for_course = ContentTypeGatingConfig.enabled_for_course( course_key=course.id) studio_override_for_course = ContentTypeGatingConfig.current( course_key=course.id).studio_override_enabled if not (enabled_for_course or studio_override_for_course): return None try: content_gate_scheme = UserPartition.get_scheme( CONTENT_TYPE_GATING_SCHEME) except UserPartitionError: LOG.warning( u"No %r scheme registered, ContentTypeGatingPartitionScheme will not be created.", CONTENT_TYPE_GATING_SCHEME) return None used_ids = set(p.id for p in course.user_partitions) if CONTENT_GATING_PARTITION_ID in used_ids: # It's possible for course authors to add arbitrary partitions via XML import. If they do, and create a # partition with id 51, it will collide with the Content Gating Partition. We'll catch that here, and # then fix the course content as needed (or get the course team to). LOG.warning( u"Can't add %r partition, as ID %r is assigned to %r in course %s.", CONTENT_TYPE_GATING_SCHEME, CONTENT_GATING_PARTITION_ID, _get_partition_from_id(course.user_partitions, CONTENT_GATING_PARTITION_ID).name, six.text_type(course.id), ) return None partition = content_gate_scheme.create_user_partition( id=CONTENT_GATING_PARTITION_ID, # Content gating partition name should not be marked for translations # edX mobile apps expect it in english name=u"Feature-based Enrollments", description=_( u"Partition for segmenting users by access to gated content types" ), parameters={"course_id": six.text_type(course.id)}) return partition
def _get_course_duration_info(self, course_key): """ Fetch course duration information from database """ try: key = CourseKey.from_string(course_key) course = CourseOverview.objects.values('display_name').get(id=key) duration_config = CourseDurationLimitConfig.current(course_key=key) gating_config = ContentTypeGatingConfig.current(course_key=key) duration_enabled = CourseDurationLimitConfig.enabled_for_course( course_key=key) gating_enabled = ContentTypeGatingConfig.enabled_for_course( course_key=key) gating_dict = { 'enabled': gating_enabled, 'enabled_as_of': str(gating_config.enabled_as_of) if gating_config.enabled_as_of else 'N/A', 'reason': gating_config.provenances['enabled'].value } duration_dict = { 'enabled': duration_enabled, 'enabled_as_of': str(duration_config.enabled_as_of) if duration_config.enabled_as_of else 'N/A', 'reason': duration_config.provenances['enabled'].value } return { 'course_id': course_key, 'course_name': course.get('display_name'), 'gating_config': gating_dict, 'duration_config': duration_dict, } except (ObjectDoesNotExist, InvalidKeyError): return {}
def create_content_gating_partition(course): """ Create and return the Content Gating user partition. """ enabled_for_course = ContentTypeGatingConfig.enabled_for_course(course_key=course.id) studio_override_for_course = ContentTypeGatingConfig.current(course_key=course.id).studio_override_enabled if not (enabled_for_course or studio_override_for_course): return None try: content_gate_scheme = UserPartition.get_scheme(CONTENT_TYPE_GATING_SCHEME) except UserPartitionError: LOG.warning( u"No %r scheme registered, ContentTypeGatingPartitionScheme will not be created.", CONTENT_TYPE_GATING_SCHEME ) return None used_ids = set(p.id for p in course.user_partitions) if CONTENT_GATING_PARTITION_ID in used_ids: # It's possible for course authors to add arbitrary partitions via XML import. If they do, and create a # partition with id 51, it will collide with the Content Gating Partition. We'll catch that here, and # then fix the course content as needed (or get the course team to). LOG.warning( u"Can't add %r partition, as ID %r is assigned to %r in course %s.", CONTENT_TYPE_GATING_SCHEME, CONTENT_GATING_PARTITION_ID, _get_partition_from_id(course.user_partitions, CONTENT_GATING_PARTITION_ID).name, unicode(course.id), ) return None partition = content_gate_scheme.create_user_partition( id=CONTENT_GATING_PARTITION_ID, name=_(u"Feature-based Enrollments"), description=_(u"Partition for segmenting users by access to gated content types"), parameters={"course_id": unicode(course.id)} ) return partition
def enabled_for(cls, course): """Check our stackable config for this specific course""" return ContentTypeGatingConfig.enabled_for_course( course_key=course.scope_ids.usage_id.course_key)
def enabled_for(cls, course): """This simple override provider is always enabled""" return ContentTypeGatingConfig.enabled_for_course( course_key=course.scope_ids.usage_id.course_key)
def get(self, request, course_id, error=None): """Displays the course mode choice page. Args: request (`Request`): The Django Request object. course_id (unicode): The slash-separated course key. Keyword Args: error (unicode): If provided, display this error message on the page. Returns: Response """ course_key = CourseKey.from_string(course_id) # Check whether the user has access to this course # based on country access rules. embargo_redirect = embargo_api.redirect_if_blocked( course_key, user=request.user, ip_address=get_ip(request), url=request.path ) if embargo_redirect: return redirect(embargo_redirect) enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user(request.user, course_key) modes = CourseMode.modes_for_course_dict(course_key) ecommerce_service = EcommerceService() # We assume that, if 'professional' is one of the modes, it should be the *only* mode. # If there are both modes, default to non-id-professional. has_enrolled_professional = (CourseMode.is_professional_slug(enrollment_mode) and is_active) if CourseMode.has_professional_mode(modes) and not has_enrolled_professional: purchase_workflow = request.GET.get("purchase_workflow", "single") verify_url = reverse('verify_student_start_flow', kwargs={'course_id': unicode(course_key)}) redirect_url = "{url}?purchase_workflow={workflow}".format(url=verify_url, workflow=purchase_workflow) if ecommerce_service.is_enabled(request.user): professional_mode = modes.get(CourseMode.NO_ID_PROFESSIONAL_MODE) or modes.get(CourseMode.PROFESSIONAL) if purchase_workflow == "single" and professional_mode.sku: redirect_url = ecommerce_service.get_checkout_page_url(professional_mode.sku) if purchase_workflow == "bulk" and professional_mode.bulk_sku: redirect_url = ecommerce_service.get_checkout_page_url(professional_mode.bulk_sku) return redirect(redirect_url) course = modulestore().get_course(course_key) # If there isn't a verified mode available, then there's nothing # to do on this page. Send the user to the dashboard. if not CourseMode.has_verified_mode(modes): return redirect(reverse('dashboard')) # If a user has already paid, redirect them to the dashboard. if is_active and (enrollment_mode in CourseMode.VERIFIED_MODES + [CourseMode.NO_ID_PROFESSIONAL_MODE]): # If the course has started redirect to course home instead if course.has_started(): return redirect(reverse('openedx.course_experience.course_home', kwargs={'course_id': course_key})) return redirect(reverse('dashboard')) donation_for_course = request.session.get("donation_for_course", {}) chosen_price = donation_for_course.get(unicode(course_key), None) if CourseEnrollment.is_enrollment_closed(request.user, course): locale = to_locale(get_language()) enrollment_end_date = format_datetime(course.enrollment_end, 'short', locale=locale) params = urllib.urlencode({'course_closed': enrollment_end_date}) return redirect('{0}?{1}'.format(reverse('dashboard'), params)) # When a credit mode is available, students will be given the option # to upgrade from a verified mode to a credit mode at the end of the course. # This allows students who have completed photo verification to be eligible # for univerity credit. # Since credit isn't one of the selectable options on the track selection page, # we need to check *all* available course modes in order to determine whether # a credit mode is available. If so, then we show slightly different messaging # for the verified track. has_credit_upsell = any( CourseMode.is_credit_mode(mode) for mode in CourseMode.modes_for_course(course_key, only_selectable=False) ) course_id = text_type(course_key) bundle_data = {} bundles_on_track_selection = WaffleFlag(WaffleFlagNamespace(name=u'experiments'), u'bundles_on_track_selection') if bundles_on_track_selection.is_enabled(): # enrollment in the course on this page current_enrollment = list(CourseEnrollment.enrollments_for_user(request.user).filter(course_id=course_key)) if current_enrollment: meter = ProgramProgressMeter(request.site, request.user, enrollments=current_enrollment) meter_inverted_programs = meter.invert_programs() if len(meter_inverted_programs) > 0: # program for the course on this page programs_for_course = meter_inverted_programs.get(course_id) if programs_for_course: program_for_course = programs_for_course[0] program_uuid = program_for_course.get('uuid') if program_for_course: # program data with bundle info program_data = ProgramDataExtender(program_for_course, request.user, mobile_only=False).extend() skus = program_data.get('skus') ecommerce_service = EcommerceService() program_bundle_url = ecommerce_service.get_checkout_page_url(*skus, program_uuid=program_uuid) bundle_data = { 'program_marketing_site_url': program_data.get('marketing_url'), 'program_bundle_url': program_bundle_url, 'discount_data': program_data.get('discount_data'), 'program_type': program_data.get('type'), 'program_title': program_data.get('title'), 'program_price': program_data.get('full_program_price'), } context = { "bundle_data": bundle_data, "course_modes_choose_url": reverse( "course_modes_choose", kwargs={'course_id': course_id} ), "modes": modes, "has_credit_upsell": has_credit_upsell, "course_name": course.display_name_with_default, "course_org": course.display_org_with_default, "course_num": course.display_number_with_default, "chosen_price": chosen_price, "error": error, "responsive": True, "nav_hidden": True, "content_gating_enabled": ContentTypeGatingConfig.enabled_for_course(course_key=course_key), } context.update( get_experiment_user_metadata_context( course, request.user, ) ) title_content = _("Congratulations! You are now enrolled in {course_name}").format( course_name=course.display_name_with_default ) context["title_content"] = title_content if "verified" in modes: verified_mode = modes["verified"] context["suggested_prices"] = [ decimal.Decimal(x.strip()) for x in verified_mode.suggested_prices.split(",") if x.strip() ] context["currency"] = verified_mode.currency.upper() context["min_price"] = verified_mode.min_price context["verified_name"] = verified_mode.name context["verified_description"] = verified_mode.description if verified_mode.sku: context["use_ecommerce_payment_flow"] = ecommerce_service.is_enabled(request.user) context["ecommerce_payment_page"] = ecommerce_service.payment_page_url() context["sku"] = verified_mode.sku context["bulk_sku"] = verified_mode.bulk_sku context['currency_data'] = [] if waffle.switch_is_active('local_currency'): if 'edx-price-l10n' not in request.COOKIES: currency_data = get_currency_data() try: context['currency_data'] = json.dumps(currency_data) except TypeError: pass return render_to_response("course_modes/choose.html", context)
def enabled_for(cls, course): """This simple override provider is always enabled""" return ContentTypeGatingConfig.enabled_for_course(course_key=course.scope_ids.usage_id.course_key)
def enabled_for(cls, course): # pylint: disable=arguments-differ """Check our stackable config for this specific course""" return ContentTypeGatingConfig.enabled_for_course( course_key=course.scope_ids.usage_id.course_key)