def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) self.perform_create(serializer) entitlement = serializer.instance user = entitlement.user # find all course_runs within the course course_runs = get_course_runs_for_course(entitlement.course_uuid) # check if the user has enrollments for any of the course_runs user_run_enrollments = [ CourseEnrollment.get_enrollment(user, CourseKey.from_string(course_run.get('key'))) for course_run in course_runs if CourseEnrollment.get_enrollment(user, CourseKey.from_string(course_run.get('key'))) ] # filter to just enrollments that can be upgraded. upgradeable_enrollments = [ enrollment for enrollment in user_run_enrollments if enrollment.is_active and enrollment.upgrade_deadline and enrollment.upgrade_deadline > timezone.now() ] # if there is only one upgradeable enrollment, convert it from audit to the entitlement.mode # if there is any ambiguity about which enrollment to upgrade # (i.e. multiple upgradeable enrollments or no available upgradeable enrollment), dont enroll if len(upgradeable_enrollments) == 1: enrollment = upgradeable_enrollments[0] log.info( 'Upgrading enrollment [%s] from %s to %s while adding entitlement for user [%s] for course [%s]', enrollment, enrollment.mode, serializer.data.get('mode'), user.username, serializer.data.get('course_uuid') ) enrollment.update_enrollment(mode=entitlement.mode) entitlement.set_enrollment(enrollment) else: log.info( 'No enrollment upgraded while adding entitlement for user [%s] for course [%s] ', user.username, serializer.data.get('course_uuid') ) headers = self.get_success_headers(serializer.data) # Note, the entitlement is re-serialized before getting added to the Response, # so that the 'modified' date reflects changes that occur when upgrading enrollment. return Response( CourseEntitlementSerializer(entitlement).data, status=status.HTTP_201_CREATED, headers=headers )
def test_course_home_for_global_staff(self): """ Tests that staff user can access the course home without being enrolled in the course. """ course = self.course self.user.is_staff = True self.user.save() self.override_waffle_switch(True) CourseEnrollment.get_enrollment(self.user, course.id).delete() response = self.visit_course_home(course, start_count=1, resume_count=0) content = pq(response.content) self.assertTrue(content('.action-resume-course').attr('href').endswith('/course/' + course.url_name))
def sync_cohort_with_mode(course_id, user_id, verified_cohort_name): """ If the learner's mode does not match their assigned cohort, move the learner into the correct cohort. It is assumed that this task is only initiated for courses that are using the Automatic Verified Track Cohorting MVP feature. It is also assumed that before initiating this task, verification has been done to ensure that the course is cohorted and has an appropriately named "verified" cohort. """ course_key = CourseKey.from_string(course_id) user = User.objects.get(id=user_id) enrollment = CourseEnrollment.get_enrollment(user, course_key) # Note that this will enroll the user in the default cohort on initial enrollment. # That's good because it will force creation of the default cohort if necessary. current_cohort = get_cohort(user, course_key) verified_cohort = get_cohort_by_name(course_key, verified_cohort_name) if enrollment.mode == CourseMode.VERIFIED and (current_cohort.id != verified_cohort.id): LOGGER.info( "MOVING_TO_VERIFIED: Moving user '%s' to the verified cohort for course '%s'", user.username, course_id ) add_user_to_cohort(verified_cohort, user.username) elif enrollment.mode != CourseMode.VERIFIED and current_cohort.id == verified_cohort.id: LOGGER.info( "MOVING_TO_DEFAULT: Moving user '%s' to the default cohort for course '%s'", user.username, course_id ) default_cohort = get_cohort_by_name(course_key, DEFAULT_COHORT_NAME) add_user_to_cohort(default_cohort, user.username) else: LOGGER.info( "NO_ACTION_NECESSARY: No action necessary for user '%s' in course '%s' and enrollment mode '%s'", user.username, course_id, enrollment.mode )
def course_run_refund_status(request, course_id): """ Get Refundable status for a course. Arguments: request: The request object. course_id (str): The unique identifier for the course. Returns: Json response. """ try: course_key = CourseKey.from_string(course_id) course_enrollment = CourseEnrollment.get_enrollment(request.user, course_key) except InvalidKeyError: logging.exception("The course key used to get refund status caused InvalidKeyError during look up.") return JsonResponse({'course_refundable_status': ''}, status=406) refundable_status = course_enrollment.refundable() logging.info("Course refund status for course {0} is {1}".format(course_id, refundable_status)) return JsonResponse({'course_refundable_status': refundable_status}, status=200)
def handle(self, *args, **options): csv_path = options['csv_path'] with open(csv_path) as csvfile: reader = unicodecsv.DictReader(csvfile) for row in reader: username = row['username'] email = row['email'] course_key = row['course_id'] try: user = User.objects.get(Q(username=username) | Q(email=email)) except ObjectDoesNotExist: user = None msg = 'User with username {} or email {} does not exist'.format(username, email) logger.warning(msg) try: course_id = CourseKey.from_string(course_key) except InvalidKeyError: course_id = None msg = 'Invalid course id {course_id}, skipping un-enrollement for {username}, {email}'.format(**row) logger.warning(msg) if user and course_id: enrollment = CourseEnrollment.get_enrollment(user, course_id) if not enrollment: msg = 'Enrollment for the user {} in course {} does not exist!'.format(username, course_key) logger.info(msg) else: try: CourseEnrollment.unenroll(user, course_id, skip_refund=True) except Exception as err: msg = 'Error un-enrolling User {} from course {}: '.format(username, course_key, err) logger.error(msg, exc_info=True)
def register_alerts(self, request, course): """ Registers an alert if the course has not started yet. """ is_enrolled = CourseEnrollment.get_enrollment(request.user, course.id) if not course.start or not is_enrolled: return days_until_start = (course.start - self.current_time).days if course.start > self.current_time: if days_until_start > 0: CourseHomeMessages.register_info_message( request, Text(_( "Don't forget to add a calendar reminder!" )), title=Text(_("Course starts in {time_remaining_string} on {course_start_date}.")).format( time_remaining_string=self.time_remaining_string, course_start_date=self.long_date_html, ) ) else: CourseHomeMessages.register_info_message( request, Text(_("Course starts in {time_remaining_string} at {course_start_time}.")).format( time_remaining_string=self.time_remaining_string, course_start_time=self.short_time_html, ) )
def register_alerts(self, request, course): """ Registers an alert if the end date is approaching. """ is_enrolled = CourseEnrollment.get_enrollment(request.user, course.id) if not course.start or self.current_time < course.start or not is_enrolled: return days_until_end = (course.end - self.current_time).days if course.end > self.current_time and days_until_end <= settings.COURSE_MESSAGE_ALERT_DURATION_IN_DAYS: if days_until_end > 0: CourseHomeMessages.register_info_message( request, Text(self.description), title=Text(_('This course is ending in {time_remaining_string} on {course_end_date}.')).format( time_remaining_string=self.time_remaining_string, course_end_date=self.long_date_html, ) ) else: CourseHomeMessages.register_info_message( request, Text(self.description), title=Text(_('This course is ending in {time_remaining_string} at {course_end_time}.')).format( time_remaining_string=self.time_remaining_string, course_end_time=self.short_time_html, ) )
def check_and_get_upgrade_link_and_date(user, enrollment=None, course=None): """ For an authenticated user, return a link to allow them to upgrade in the specified course. """ if enrollment is None and course is None: raise ValueError("Must specify either an enrollment or a course") if enrollment: if course is None: course = enrollment.course elif enrollment.course_id != course.id: raise ValueError("{} refers to a different course than {} which was supplied".format( enrollment, course )) if enrollment.user_id != user.id: raise ValueError("{} refers to a different user than {} which was supplied".format( enrollment, user )) if enrollment is None: enrollment = CourseEnrollment.get_enrollment(user, course.id) if user.is_authenticated and verified_upgrade_link_is_valid(enrollment): return ( verified_upgrade_deadline_link(user, course), enrollment.upgrade_deadline ) return (None, None)
def get_upgradeable_enrollments_for_entitlement(self, entitlement): """ Retrieve all the CourseEnrollments that are upgradeable for a given CourseEntitlement Arguments: entitlement: CourseEntitlement that we are requesting the CourseEnrollments for. Returns: list: List of upgradeable CourseEnrollments """ # find all course_runs within the course course_runs = get_course_runs_for_course(entitlement.course_uuid) # check if the user has enrollments for any of the course_runs upgradeable_enrollments = [] for course_run in course_runs: course_run_id = CourseKey.from_string(course_run.get('key')) enrollment = CourseEnrollment.get_enrollment(entitlement.user, course_run_id) if (enrollment and enrollment.is_active and is_course_run_entitlement_fulfillable(course_run_id, entitlement)): upgradeable_enrollments.append(enrollment) return upgradeable_enrollments
def add_or_update_enrollment_attr(user_id, course_id, attributes): """Set enrollment attributes for the enrollment of given user in the course provided. Args: course_id (str): The Course to set enrollment attributes for. user_id (str): The User to set enrollment attributes for. attributes (list): Attributes to be set. Example: >>>add_or_update_enrollment_attr( "Bob", "course-v1-edX-DemoX-1T2015", [ { "namespace": "credit", "name": "provider_id", "value": "hogwarts", }, ] ) """ course_key = CourseKey.from_string(course_id) user = _get_user(user_id) enrollment = CourseEnrollment.get_enrollment(user, course_key) if not _invalid_attribute(attributes) and enrollment is not None: CourseEnrollmentAttribute.add_enrollment_attr(enrollment, attributes)
def get_credit_state(self, user_id, course_key_or_id, return_course_info=False): """ Return all information about the user's credit state inside of a given course. ARGS: - user_id: The PK of the User in question - course_key: The course ID (as string or CourseKey) RETURNS: NONE (user not found or is not enrolled or is not credit course) - or - { 'enrollment_mode': the mode that the user is enrolled in the course 'profile_fullname': the name that the student registered under, used for verification 'is_credit_course': if the course has been marked as a credit bearing course 'credit_requirement_status': the user's status in fulfilling those requirements 'course_name': optional display name of the course 'course_end_date': optional end date of the course } """ # This seems to need to be here otherwise we get # circular references when starting up the app from openedx.core.djangoapps.credit.api.eligibility import ( is_credit_course, get_credit_requirement_status, ) # since we have to do name matching during various # verifications, User must have a UserProfile try: user = User.objects.select_related('profile').get(id=user_id) except ObjectDoesNotExist: # bad user_id return None course_key = _get_course_key(course_key_or_id) enrollment = CourseEnrollment.get_enrollment(user, course_key) if not enrollment or not enrollment.is_active: # not enrolled return None result = { 'enrollment_mode': enrollment.mode, 'profile_fullname': user.profile.name, 'student_email': user.email, 'is_credit_course': is_credit_course(course_key), 'credit_requirement_status': get_credit_requirement_status(course_key, user.username) } if return_course_info: course = modulestore().get_course(course_key, depth=0) result.update({ 'course_name': course.display_name, 'course_end_date': course.end, }) return result
def enabled_for_enrollment(cls, enrollment=None, user=None, course_key=None): """ Return whether Content Type Gating is enabled for this enrollment. Content Type Gating is enabled for an enrollment if it is enabled for the course being enrolled in (either specifically, or via a containing context, such as the org, site, or globally), and if the configuration is specified to be ``enabled_as_of`` before the enrollment was created. Only one of enrollment and (user, course_key) may be specified at a time. Arguments: enrollment: The enrollment being queried. user: The user being queried. course_key: The CourseKey of the course being queried. """ if CONTENT_TYPE_GATING_FLAG.is_enabled(): return True if enrollment is not None and (user is not None or course_key is not None): raise ValueError('Specify enrollment or user/course_key, but not both') if enrollment is None and (user is None or course_key is None): raise ValueError('Both user and course_key must be specified if no enrollment is provided') if enrollment is None and user is None and course_key is None: raise ValueError('At least one of enrollment or user and course_key must be specified') if course_key is None: course_key = enrollment.course_id if enrollment is None: enrollment = CourseEnrollment.get_enrollment(user, course_key) # enrollment might be None if the user isn't enrolled. In that case, # return enablement as if the user enrolled today if enrollment is None: return cls.enabled_for_course(course_key=course_key, target_datetime=timezone.now()) else: # TODO: clean up as part of REV-100 experiment_data_holdback_key = EXPERIMENT_DATA_HOLDBACK_KEY.format(user) is_in_holdback = False no_masquerade = get_course_masquerade(user, course_key) is None student_masquerade = is_masquerading_as_specific_student(user, course_key) if user and (no_masquerade or student_masquerade): try: holdback_value = ExperimentData.objects.get( user=user, experiment_id=EXPERIMENT_ID, key=experiment_data_holdback_key, ).value is_in_holdback = holdback_value == 'True' except ExperimentData.DoesNotExist: pass if is_in_holdback: return False current_config = cls.current(course_key=enrollment.course_id) return current_config.enabled_as_of_datetime(target_datetime=enrollment.created)
def get_enrollment_info(self, user, course_id): """ Returns the User Enrollment information. """ course = get_course_by_id(course_id, depth=0) is_course_staff = bool(has_access(user, 'staff', course)) manual_enrollment_reason = 'N/A' # check the user enrollment role if user.is_staff: platform_name = configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME) enrollment_role = _('{platform_name} Staff').format(platform_name=platform_name) elif is_course_staff: enrollment_role = _('Course Staff') else: enrollment_role = _('Student') course_enrollment = CourseEnrollment.get_enrollment(user=user, course_key=course_id) if is_course_staff: enrollment_source = _('Staff') else: # get the registration_code_redemption object if exists registration_code_redemption = RegistrationCodeRedemption.registration_code_used_for_enrollment( course_enrollment) # get the paid_course registration item if exists paid_course_reg_item = PaidCourseRegistration.get_course_item_for_user_enrollment( user=user, course_id=course_id, course_enrollment=course_enrollment ) # from where the user get here if registration_code_redemption is not None: enrollment_source = _('Used Registration Code') elif paid_course_reg_item is not None: enrollment_source = _('Credit Card - Individual') else: manual_enrollment = ManualEnrollmentAudit.get_manual_enrollment(course_enrollment) if manual_enrollment is not None: enrollment_source = _( 'manually enrolled by username: {username}' ).format(username=manual_enrollment.enrolled_by.username) manual_enrollment_reason = manual_enrollment.reason else: enrollment_source = _('Manually Enrolled') enrollment_date = course_enrollment.created.strftime("%B %d, %Y") currently_enrolled = course_enrollment.is_active course_enrollment_data = collections.OrderedDict() course_enrollment_data['Enrollment Date'] = enrollment_date course_enrollment_data['Currently Enrolled'] = currently_enrolled course_enrollment_data['Enrollment Source'] = enrollment_source course_enrollment_data['Manual (Un)Enrollment Reason'] = manual_enrollment_reason course_enrollment_data['Enrollment Role'] = enrollment_role return course_enrollment_data
def post(self, request, *args, **kwargs): """ Attempt to enroll the user. """ user = request.user valid, course_key, error = self._is_data_valid(request) if not valid: return DetailResponse(error, status=HTTP_406_NOT_ACCEPTABLE) embargo_response = embargo_api.get_embargo_response(request, course_key, user) if embargo_response: return embargo_response # Don't do anything if an enrollment already exists course_id = unicode(course_key) enrollment = CourseEnrollment.get_enrollment(user, course_key) if enrollment and enrollment.is_active: msg = Messages.ENROLLMENT_EXISTS.format(course_id=course_id, username=user.username) return DetailResponse(msg, status=HTTP_409_CONFLICT) # Check to see if enrollment for this course is closed. course = courses.get_course(course_key) if CourseEnrollment.is_enrollment_closed(user, course): msg = Messages.ENROLLMENT_CLOSED.format(course_id=course_id) log.info(u'Unable to enroll user %s in closed course %s.', user.id, course_id) return DetailResponse(msg, status=HTTP_406_NOT_ACCEPTABLE) # If there is no audit or honor course mode, this most likely # a Prof-Ed course. Return an error so that the JS redirects # to track selection. honor_mode = CourseMode.mode_for_course(course_key, CourseMode.HONOR) audit_mode = CourseMode.mode_for_course(course_key, CourseMode.AUDIT) # Accept either honor or audit as an enrollment mode to # maintain backwards compatibility with existing courses default_enrollment_mode = audit_mode or honor_mode if default_enrollment_mode: msg = Messages.ENROLL_DIRECTLY.format( username=user.username, course_id=course_id ) if not default_enrollment_mode.sku: # If there are no course modes with SKUs, return a different message. msg = Messages.NO_SKU_ENROLLED.format( enrollment_mode=default_enrollment_mode.slug, course_id=course_id, username=user.username ) log.info(msg) self._enroll(course_key, user, default_enrollment_mode.slug) self._handle_marketing_opt_in(request, course_key, user) return DetailResponse(msg) else: msg = Messages.NO_DEFAULT_ENROLLMENT_MODE.format(course_id=course_id) return DetailResponse(msg, status=HTTP_406_NOT_ACCEPTABLE)
def test_lookup_valid_redeemed_registration_code(self): """ test to lookup for the valid and redeemed registration code and then mark that registration code as un_redeemed which will unenroll the user and delete the redemption entry from the database. """ student = UserFactory() self.client.login(username=student.username, password='******') cart = Order.get_cart_for_user(student) cart.order_type = 'business' cart.save() CourseRegCodeItem.add_to_order(cart, self.course.id, 2) cart.purchase() reg_code = CourseRegistrationCode.objects.filter(order=cart)[0] enrollment = CourseEnrollment.enroll(student, self.course.id) RegistrationCodeRedemption.objects.create( registration_code=reg_code, redeemed_by=student, course_enrollment=enrollment ) self.client.login(username=self.instructor.username, password='******') data = { 'registration_code': reg_code.code } response = self.client.get(self.lookup_code_url, data) self.assertEqual(response.status_code, 200) json_dict = json.loads(response.content) self.assertTrue(json_dict['is_registration_code_valid']) self.assertTrue(json_dict['is_registration_code_redeemed']) # now mark the registration code as unredeemed # this will unenroll the user and removed the redemption entry from # the database. data = { 'registration_code': reg_code.code, 'action_type': 'unredeem_registration_code' } response = self.client.post(self.registration_code_detail_url, data) self.assertEqual(response.status_code, 200) json_dict = json.loads(response.content) message = _('This enrollment code has been marked as unused.') self.assertEqual(message, json_dict['message']) redemption = RegistrationCodeRedemption.get_registration_code_redemption(reg_code.code, self.course.id) self.assertIsNone(redemption) # now the student course enrollment should be false. enrollment = CourseEnrollment.get_enrollment(student, self.course.id) self.assertEqual(enrollment.is_active, False)
def test_look_up_valid_registration_code(self): """ test lookup for the valid registration code and that registration code has been redeemed by user and then mark the registration code as in_valid when marking as invalidate, it also lookup for registration redemption entry and also delete that redemption entry and un_enroll the student who used that registration code for their enrollment. """ for i in range(2): CourseRegistrationCode.objects.create( code="reg_code{}".format(i), course_id=unicode(self.course.id), created_by=self.instructor, invoice=self.sale_invoice, invoice_item=self.invoice_item, mode_slug="honor", ) reg_code = CourseRegistrationCode.objects.all()[0] student = UserFactory() enrollment = CourseEnrollment.enroll(student, self.course.id) RegistrationCodeRedemption.objects.create( registration_code=reg_code, redeemed_by=student, course_enrollment=enrollment ) data = {"registration_code": reg_code.code} response = self.client.get(self.lookup_code_url, data) self.assertEqual(response.status_code, 200) json_dict = json.loads(response.content) self.assertTrue(json_dict["is_registration_code_valid"]) self.assertTrue(json_dict["is_registration_code_redeemed"]) # now mark that registration code as invalid data = {"registration_code": reg_code.code, "action_type": "invalidate_registration_code"} response = self.client.post(self.registration_code_detail_url, data) self.assertEqual(response.status_code, 200) json_dict = json.loads(response.content) message = _("This enrollment code has been canceled. It can no longer be used.") self.assertEqual(message, json_dict["message"]) # now check that the registration code should be marked as invalid in the db. reg_code = CourseRegistrationCode.objects.get(code=reg_code.code) self.assertEqual(reg_code.is_valid, False) redemption = RegistrationCodeRedemption.get_registration_code_redemption(reg_code.code, self.course.id) self.assertIsNone(redemption) # now the student course enrollment should be false. enrollment = CourseEnrollment.get_enrollment(student, self.course.id) self.assertEqual(enrollment.is_active, False)
def sync_cohort_with_mode(self, course_id, user_id, verified_cohort_name, default_cohort_name): """ If the learner's mode does not match their assigned cohort, move the learner into the correct cohort. It is assumed that this task is only initiated for courses that are using the Automatic Verified Track Cohorting MVP feature. It is also assumed that before initiating this task, verification has been done to ensure that the course is cohorted and has an appropriately named "verified" cohort. """ course_key = CourseKey.from_string(course_id) user = User.objects.get(id=user_id) try: enrollment = CourseEnrollment.get_enrollment(user, course_key) # Note that this will enroll the user in the default cohort on initial enrollment. # That's good because it will force creation of the default cohort if necessary. try: current_cohort = get_cohort(user, course_key) except IntegrityError as integrity_error: # It is quite common that get_cohort will throw an IntegrityError. This happens # when 2 celery workers are both handling enrollment change events for the same user # (for example, if the enrollment mode goes from None -> Audit -> Honor); if the user # was not previously in a cohort, calling get_cohort will result in a cohort assignment. LOGGER.info( "HANDLING_INTEGRITY_ERROR: IntegrityError encountered for course '%s' and user '%s': %s", course_id, user.id, unicode(integrity_error) ) current_cohort = get_cohort(user, course_key) verified_cohort = get_cohort_by_name(course_key, verified_cohort_name) if enrollment.mode == CourseMode.VERIFIED and (current_cohort.id != verified_cohort.id): LOGGER.info( "MOVING_TO_VERIFIED: Moving user '%s' to the verified cohort '%s' for course '%s'", user.id, verified_cohort.name, course_id ) add_user_to_cohort(verified_cohort, user.username) elif enrollment.mode != CourseMode.VERIFIED and current_cohort.id == verified_cohort.id: default_cohort = get_cohort_by_name(course_key, default_cohort_name) LOGGER.info( "MOVING_TO_DEFAULT: Moving user '%s' to the default cohort '%s' for course '%s'", user.id, default_cohort.name, course_id ) add_user_to_cohort(default_cohort, user.username) else: LOGGER.info( "NO_ACTION_NECESSARY: No action necessary for user '%s' in course '%s' and enrollment mode '%s'. " "The user is already in cohort '%s'.", user.id, course_id, enrollment.mode, current_cohort.name ) except Exception as exc: LOGGER.warning( "SYNC_COHORT_WITH_MODE_RETRY: Exception encountered for course '%s' and user '%s': %s", course_id, user.id, unicode(exc) ) raise self.retry(exc=exc)
def upgrade_url(self, user, course_key): """ Returns the URL for the user to upgrade, or None if not applicable. """ enrollment = CourseEnrollment.get_enrollment(user, course_key) verified_mode = enrollment.verified_mode if enrollment else None if verified_mode: if self.is_enabled(user): return self.get_checkout_page_url(verified_mode.sku) else: return reverse('verify_student_upgrade_and_verify', args=(course_key,)) return None
def check_and_get_upgrade_link_and_date(user, enrollment=None, course=None): """ For an authenticated user, return a link to allow them to upgrade in the specified course. Returns the upgrade link and upgrade deadline for a user in a given course given that the user is within the window to upgrade defined by our dynamic pacing feature; otherwise, returns None for both the link and date. """ if enrollment is None and course is None: logger.warn(u'Must specify either an enrollment or a course') return (None, None) if enrollment: if course is None: course = enrollment.course elif enrollment.course_id != course.id: logger.warn(u'{} refers to a different course than {} which was supplied. Enrollment course id={}, ' u'repr={!r}, deprecated={}. Course id={}, repr={!r}, deprecated={}.' .format(enrollment, course, enrollment.course_id, enrollment.course_id, enrollment.course_id.deprecated, course.id, course.id, course.id.deprecated ) ) return (None, None) if enrollment.user_id != user.id: logger.warn(u'{} refers to a different user than {} which was supplied. Enrollment user id={}, repr={!r}. ' u'User id={}, repr={!r}.'.format(enrollment, user, enrollment.user_id, enrollment.user_id, user.id, user.id, ) ) return (None, None) if enrollment is None: enrollment = CourseEnrollment.get_enrollment(user, course.id) if user.is_authenticated and verified_upgrade_link_is_valid(enrollment): return ( verified_upgrade_deadline_link(user, course), enrollment.upgrade_deadline ) return (None, None)
def test_bulk_enrollment(self): """ Test all users are enrolled using the command.""" lines = (str(enrollment.course.id) + "," + str(enrollment.user.username) + ",verified\n" for enrollment in self.enrollments) with NamedTemporaryFile() as csv: csv = self._write_test_csv(csv, lines=lines) call_command("bulk_change_enrollment_csv", "--csv_file_path={}".format(csv.name)) for enrollment in self.enrollments: new_enrollment = CourseEnrollment.get_enrollment(user=enrollment.user, course_key=enrollment.course) self.assertEqual(new_enrollment.is_active, True) self.assertEqual(new_enrollment.mode, CourseMode.VERIFIED)
def get_enrollment_info(self, user, course_id): """ Returns the User Enrollment information. """ course = get_course_by_id(course_id, depth=0) is_course_staff = has_access(user, 'staff', course) # check the user enrollment role if user.is_staff: enrollment_role = _('Edx Staff') elif is_course_staff: enrollment_role = _('Course Staff') else: enrollment_role = _('Student') course_enrollment = CourseEnrollment.get_enrollment(user=user, course_key=course_id) if is_course_staff: enrollment_source = _('Staff') else: # get the registration_code_redemption object if exists registration_code_redemption = RegistrationCodeRedemption.registration_code_used_for_enrollment( course_enrollment) # get the paid_course registration item if exists paid_course_reg_item = PaidCourseRegistration.get_course_item_for_user_enrollment( user=user, course_id=course_id, course_enrollment=course_enrollment ) # from where the user get here if registration_code_redemption is not None: enrollment_source = _('Used Registration Code') elif paid_course_reg_item is not None: enrollment_source = _('Credit Card - Individual') else: enrollment_source = _('Manually Enrolled') enrollment_date = course_enrollment.created.strftime("%B %d, %Y") currently_enrolled = course_enrollment.is_active course_enrollment_data = collections.OrderedDict() course_enrollment_data['Enrollment Date'] = enrollment_date course_enrollment_data['Currently Enrolled'] = currently_enrolled course_enrollment_data['Enrollment Source'] = enrollment_source course_enrollment_data['Enrollment Role'] = enrollment_role return course_enrollment_data
def get_user_course_expiration_date(user, course): """ Return expiration date for given user course pair. Return None if the course does not expire. Business Logic: - Course access duration is bounded by the min and max duration. - If course fields are missing, default course access duration to MIN_DURATION. """ access_duration = MIN_DURATION verified_mode = CourseMode.verified_mode_for_course(course=course, include_expired=True) if not verified_mode: return None enrollment = CourseEnrollment.get_enrollment(user, course.id) if enrollment is None or enrollment.mode != CourseMode.AUDIT: return None try: # Content availability date is equivalent to max(enrollment date, course start date) # for most people. Using the schedule date will provide flexibility to deal with # more complex business rules in the future. content_availability_date = enrollment.schedule.start # We have anecdotally observed a case where the schedule.start was # equal to the course start, but should have been equal to the enrollment start # https://openedx.atlassian.net/browse/PROD-58 # This section is meant to address that case if enrollment.created and course.start: if (content_availability_date.date() == course.start.date() and course.start < enrollment.created < timezone.now()): content_availability_date = enrollment.created except CourseEnrollment.schedule.RelatedObjectDoesNotExist: content_availability_date = max(enrollment.created, course.start) # The user course expiration date is the content availability date # plus the weeks_to_complete field from course-discovery. discovery_course_details = get_course_run_details(course.id, ['weeks_to_complete']) expected_weeks = discovery_course_details.get('weeks_to_complete') if expected_weeks: access_duration = timedelta(weeks=expected_weeks) # Course access duration is bounded by the min and max duration. access_duration = max(MIN_DURATION, min(MAX_DURATION, access_duration)) return content_availability_date + access_duration
def get_verification_context(request, course): enrollment = CourseEnrollment.get_enrollment(request.user, course.id) show_course_sock = verified_upgrade_link_is_valid(enrollment) and get_language() == 'en' if show_course_sock: upgrade_url = verified_upgrade_deadline_link(request.user, course=course) course_price = get_cosmetic_verified_display_price(course) else: upgrade_url = '' course_price = '' context = { 'show_course_sock': show_course_sock, 'course_price': course_price, 'course_id': course.id, 'upgrade_url': upgrade_url, } return context
def sync_cohort_with_mode(self, course_id, user_id, verified_cohort_name, default_cohort_name): """ If the learner's mode does not match their assigned cohort, move the learner into the correct cohort. It is assumed that this task is only initiated for courses that are using the Automatic Verified Track Cohorting MVP feature. It is also assumed that before initiating this task, verification has been done to ensure that the course is cohorted and has an appropriately named "verified" cohort. """ course_key = CourseKey.from_string(course_id) user = User.objects.get(id=user_id) try: enrollment = CourseEnrollment.get_enrollment(user, course_key) # Note that this will enroll the user in the default cohort on initial enrollment. # That's good because it will force creation of the default cohort if necessary. current_cohort = get_cohort(user, course_key) verified_cohort = get_cohort_by_name(course_key, verified_cohort_name) acceptable_modes = {CourseMode.VERIFIED, CourseMode.CREDIT_MODE} if enrollment.mode in acceptable_modes and (current_cohort.id != verified_cohort.id): LOGGER.info( u"MOVING_TO_VERIFIED: Moving user '%s' to the verified cohort '%s' for course '%s'", user.id, verified_cohort.name, course_id ) add_user_to_cohort(verified_cohort, user.username) elif enrollment.mode not in acceptable_modes and current_cohort.id == verified_cohort.id: default_cohort = get_cohort_by_name(course_key, default_cohort_name) LOGGER.info( u"MOVING_TO_DEFAULT: Moving user '%s' to the default cohort '%s' for course '%s'", user.id, default_cohort.name, course_id ) add_user_to_cohort(default_cohort, user.username) else: LOGGER.info( u"NO_ACTION_NECESSARY: No action necessary for user '%s' in course '%s' and enrollment mode '%s'. " u"The user is already in cohort '%s'.", user.id, course_id, enrollment.mode, current_cohort.name ) except Exception as exc: LOGGER.warning( u"SYNC_COHORT_WITH_MODE_RETRY: Exception encountered for course '%s' and user '%s': %s", course_id, user.id, six.text_type(exc) ) raise self.retry(exc=exc)
def _get_zendesk_custom_field_context(request): """ Construct a dictionary of data that can be stored in Zendesk custom fields. """ context = {} course_id = request.POST.get("course_id") if not course_id: return context context["course_id"] = course_id if not request.user.is_authenticated(): return context enrollment = CourseEnrollment.get_enrollment(request.user, CourseKey.from_string(course_id)) if enrollment and enrollment.is_active: context["enrollment_mode"] = enrollment.mode return context
def manual_enrollment_data(enrollment_data, course_key): """ Returns serialized information about the manual enrollment belonging to this enrollment, if it exists. Args: enrollment_data (dict): Representation of a single course enrollment. course_key (CourseKey): The course for this enrollment. Returns: None: If no manual enrollment change has been made. dict: Serialization of the latest manual enrollment change. """ user = User.objects.get(username=enrollment_data['user']) enrollment = CourseEnrollment.get_enrollment(user, course_key) manual_enrollment_audit = ManualEnrollmentAudit.get_manual_enrollment(enrollment) if manual_enrollment_audit is None: return {} return ManualEnrollmentSerializer(instance=manual_enrollment_audit).data
def manual_enrollment_data(enrollment_data, course_key): """ Returns serialized information about the manual enrollment belonging to this enrollment, if it exists. Args: enrollment_data (dict): Representation of a single course enrollment. course_key (CourseKey): The course for this enrollment. Returns: None: If no manual enrollment change has been made. dict: Serialization of the latest manual enrollment change. """ user = User.objects.get(username=enrollment_data['user']) enrollment = CourseEnrollment.get_enrollment(user, course_key) manual_enrollment_audit = ManualEnrollmentAudit.get_manual_enrollment(enrollment) if manual_enrollment_audit is None: return {} return ManualEnrollmentSerializer(instance=manual_enrollment_audit).data
def get_course_enrollment(self, course_key, user): """ If student is not enrolled in course enroll the student in free mode """ course_enrollment = CourseEnrollment.get_enrollment(user, course_key) # If student is not enrolled in course enroll the student in free mode if not course_enrollment: # try to create a enroll user in default course enrollment mode in case of # professional it will break because of no default course mode. try: course_enrollment = CourseEnrollment.get_or_create_enrollment( user=user, course_key=course_key) except Exception: # pylint: disable=broad-except # In case if no free mode is available. course_enrollment = None return course_enrollment
def sync_cohort_with_mode(self, course_id, user_id, verified_cohort_name, default_cohort_name): """ If the learner's mode does not match their assigned cohort, move the learner into the correct cohort. It is assumed that this task is only initiated for courses that are using the Automatic Verified Track Cohorting MVP feature. It is also assumed that before initiating this task, verification has been done to ensure that the course is cohorted and has an appropriately named "verified" cohort. """ course_key = CourseKey.from_string(course_id) user = User.objects.get(id=user_id) try: enrollment = CourseEnrollment.get_enrollment(user, course_key) # Note that this will enroll the user in the default cohort on initial enrollment. # That's good because it will force creation of the default cohort if necessary. current_cohort = get_cohort(user, course_key) verified_cohort = get_cohort_by_name(course_key, verified_cohort_name) acceptable_modes = {CourseMode.VERIFIED, CourseMode.CREDIT_MODE} if enrollment.mode in acceptable_modes and (current_cohort.id != verified_cohort.id): LOGGER.info( "MOVING_TO_VERIFIED: Moving user '%s' to the verified cohort '%s' for course '%s'", user.id, verified_cohort.name, course_id) add_user_to_cohort(verified_cohort, user.username) elif enrollment.mode not in acceptable_modes and current_cohort.id == verified_cohort.id: default_cohort = get_cohort_by_name(course_key, default_cohort_name) LOGGER.info( "MOVING_TO_DEFAULT: Moving user '%s' to the default cohort '%s' for course '%s'", user.id, default_cohort.name, course_id) add_user_to_cohort(default_cohort, user.username) else: LOGGER.info( "NO_ACTION_NECESSARY: No action necessary for user '%s' in course '%s' and enrollment mode '%s'. " "The user is already in cohort '%s'.", user.id, course_id, enrollment.mode, current_cohort.name) except Exception as exc: LOGGER.warning( "SYNC_COHORT_WITH_MODE_RETRY: Exception encountered for course '%s' and user '%s': %s", course_id, user.id, unicode(exc)) raise self.retry(exc=exc)
def user_organization_protection_status(user, course_key): """ Returns the organization protection status of the user related to this course If the user is in the Masters track of the course, we return the protected status. If the user is a staff of the course, we return the protection_exempt status else, we return the unprotected status """ if has_course_staff_privileges(user, course_key): return OrganizationProtectionStatus.protection_exempt enrollment = CourseEnrollment.get_enrollment(user, course_key) if enrollment and enrollment.is_active: if enrollment.mode in ORGANIZATION_PROTECTED_MODES: return OrganizationProtectionStatus.protected else: return OrganizationProtectionStatus.unprotected else: raise ValueError( 'Cannot check the org_protection status on a student [%s] not enrolled in course [%s]', user.id, course_key)
def register_alerts(self, request, course): """ Registers an alert close to the certificate delivery date. """ is_enrolled = CourseEnrollment.get_enrollment(request.user, course.id) if not is_enrolled or not self.is_enabled or course.end > self.current_time: return if self.date > self.current_time: CourseHomeMessages.register_info_message( request, Text( _('If you have earned a certificate, you will be able to access it {time_remaining_string}' ' from now. You will also be able to view your certificates on your {dashboard_link}.' )).format( time_remaining_string=self.time_remaining_string, dashboard_link=HTML('<a href="{dashboard_url}</a>'). format(dashboard_url=reverse('dashboard'))), title=Text( _('We are working on generating course certificates.')))
def get_user_course_duration(user, course): """ Return a timedelta measuring the duration of the course for a particular user. Business Logic: - Course access duration is bounded by the min and max duration. - If course fields are missing, default course access duration to MIN_DURATION. """ enrollment = CourseEnrollment.get_enrollment(user, course.id) if enrollment is None or enrollment.mode != CourseMode.AUDIT: return None verified_mode = CourseMode.verified_mode_for_course(course=course, include_expired=True) if not verified_mode: return None return get_expected_duration(course)
def get_verification_context(request, course): enrollment = CourseEnrollment.get_enrollment(request.user, course.id) show_course_sock = verified_upgrade_link_is_valid(enrollment) if show_course_sock: upgrade_url = verified_upgrade_deadline_link(request.user, course=course) course_price, _ = format_strikeout_price(request.user, course) else: upgrade_url = '' course_price = '' context = { 'show_course_sock': show_course_sock, 'course_price': course_price, 'course_id': course.id, 'upgrade_url': upgrade_url, } return context
def get_expiration_banner_text(user, course, language='en'): """ Get text for banner that messages user course expiration date for different tests that depend on it. """ expiration_date = now() + timedelta(weeks=4) upgrade_link = verified_upgrade_deadline_link(user=user, course=course) enrollment = CourseEnrollment.get_enrollment(user, course.id) upgrade_deadline = enrollment.upgrade_deadline if upgrade_deadline is None or now() < upgrade_deadline: upgrade_deadline = enrollment.course_upgrade_deadline date_string = u'<span class="localized-datetime" data-format="shortDate" \ data-datetime="{formatted_date}" data-language="{language}">{formatted_date_localized}</span>' formatted_expiration_date = date_string.format( language=language, formatted_date=expiration_date.strftime("%Y-%m-%d"), formatted_date_localized=strftime_localized(expiration_date, EXPIRATION_DATE_FORMAT_STR) ) if upgrade_deadline: formatted_upgrade_deadline = date_string.format( language=language, formatted_date=upgrade_deadline.strftime("%Y-%m-%d"), formatted_date_localized=strftime_localized(upgrade_deadline, EXPIRATION_DATE_FORMAT_STR) ) bannerText = u'<strong>Audit Access Expires {expiration_date}</strong><br>\ You lose all access to this course, including your progress, on {expiration_date}.\ <br>Upgrade by {upgrade_deadline} to get unlimited access to the course as long as it exists\ on the site. <a href="{upgrade_link}">Upgrade now<span class="sr-only"> to retain access past\ {expiration_date}</span></a>'.format( expiration_date=formatted_expiration_date, upgrade_link=upgrade_link, upgrade_deadline=formatted_upgrade_deadline ) else: bannerText = u'<strong>Audit Access Expires {expiration_date}</strong><br>\ You lose all access to this course, including your progress, on {expiration_date}.\ '.format( expiration_date=formatted_expiration_date ) return bannerText
def get_expiration_banner_text(user, course, language='en'): """ Get text for banner that messages user course expiration date for different tests that depend on it. """ expiration_date = now() + timedelta(weeks=4) upgrade_link = verified_upgrade_deadline_link(user=user, course=course) enrollment = CourseEnrollment.get_enrollment(user, course.id) upgrade_deadline = enrollment.upgrade_deadline if upgrade_deadline is None or now() < upgrade_deadline: upgrade_deadline = enrollment.course_upgrade_deadline date_string = u'<span class="localized-datetime" data-format="shortDate" \ data-datetime="{formatted_date}" data-language="{language}">{formatted_date_localized}</span>' formatted_expiration_date = date_string.format( language=language, formatted_date=expiration_date.strftime("%Y-%m-%d"), formatted_date_localized=strftime_localized(expiration_date, EXPIRATION_DATE_FORMAT_STR) ) if upgrade_deadline: formatted_upgrade_deadline = date_string.format( language=language, formatted_date=upgrade_deadline.strftime("%Y-%m-%d"), formatted_date_localized=strftime_localized(upgrade_deadline, EXPIRATION_DATE_FORMAT_STR) ) bannerText = u'<strong>Audit Access Expires {expiration_date}</strong><br>\ You lose all access to this course, including your progress, on {expiration_date}.\ <br>Upgrade by {upgrade_deadline} to get unlimited access to the course as long as it exists\ on the site. <a href="{upgrade_link}">Upgrade now<span class="sr-only"> to retain access past\ {expiration_date}</span></a>'.format( expiration_date=formatted_expiration_date, upgrade_link=upgrade_link, upgrade_deadline=formatted_upgrade_deadline ) else: bannerText = u'<strong>Audit Access Expires {expiration_date}</strong><br>\ You lose all access to this course, including your progress, on {expiration_date}.\ '.format( expiration_date=formatted_expiration_date ) return bannerText
def get_expiration_banner_text(user, course): """ Get text for banner that messages user course expiration date for different tests that depend on it. """ expiration_date = now() + timedelta(weeks=4) upgrade_link = verified_upgrade_deadline_link(user=user, course=course) enrollment = CourseEnrollment.get_enrollment(user, course.id) upgrade_deadline = enrollment.upgrade_deadline if upgrade_deadline is None or now() < upgrade_deadline: upgrade_deadline = enrollment.course_upgrade_deadline language = get_language() language_is_es = language and language.split('-')[0].lower() == 'es' if language_is_es: formatted_expiration_date = strftime_localized( expiration_date, '%-d de %b. de %Y').lower() else: formatted_expiration_date = strftime_localized(expiration_date, '%b. %-d, %Y') if upgrade_deadline: if language_is_es: formatted_upgrade_deadline = strftime_localized( upgrade_deadline, '%-d de %b. de %Y').lower() else: formatted_upgrade_deadline = strftime_localized( upgrade_deadline, '%b. %-d, %Y') bannerText = '<strong>Audit Access Expires {expiration_date}</strong><br>\ You lose all access to this course, including your progress, on {expiration_date}.\ <br>Upgrade by {upgrade_deadline} to get unlimited access to the course as long as it exists\ on the site. <a href="{upgrade_link}">Upgrade now<span class="sr-only"> to retain access past\ {expiration_date}</span></a>'.format( expiration_date=formatted_expiration_date, upgrade_link=upgrade_link, upgrade_deadline=formatted_upgrade_deadline) else: bannerText = '<strong>Audit Access Expires {expiration_date}</strong><br>\ You lose all access to this course, including your progress, on {expiration_date}.\ '.format(expiration_date=formatted_expiration_date) return bannerText
def __init__(self, course_key, request, username=''): self.overview = course_detail( request, username or request.user.username, course_key, ) self.original_user_is_staff = has_access(request.user, 'staff', self.overview).has_access self.course_key = course_key self.course_masquerade, self.effective_user = setup_masquerade( request, course_key, staff_access=self.original_user_is_staff, ) self.is_staff = has_access(self.effective_user, 'staff', self.overview).has_access self.enrollment_object = CourseEnrollment.get_enrollment( self.effective_user, self.course_key, select_related=['celebration'])
def get_verification_context(request, course): enrollment = CourseEnrollment.get_enrollment(request.user, course.id) show_course_sock = verified_upgrade_link_is_valid( enrollment) and get_language() == 'en' if show_course_sock: upgrade_url = verified_upgrade_deadline_link(request.user, course=course) course_price = get_cosmetic_verified_display_price(course) else: upgrade_url = '' course_price = '' context = { 'show_course_sock': show_course_sock, 'course_price': course_price, 'course_id': course.id, 'upgrade_url': upgrade_url, } return context
def _get_zendesk_custom_field_context(request): """ Construct a dictionary of data that can be stored in Zendesk custom fields. """ context = {} course_id = request.POST.get("course_id") if not course_id: return context context["course_id"] = course_id if not request.user.is_authenticated(): return context enrollment = CourseEnrollment.get_enrollment( request.user, CourseKey.from_string(course_id)) if enrollment and enrollment.is_active: context["enrollment_mode"] = enrollment.mode return context
def handle(self, *args, **options): csv_path = options['csv_path'] with open(csv_path) as csvfile: reader = unicodecsv.DictReader(csvfile) for row in reader: username = row['username'] email = row['email'] course_key = row['course_id'] try: user = User.objects.get( Q(username=username) | Q(email=email)) except ObjectDoesNotExist: user = None msg = 'User with username {} or email {} does not exist'.format( username, email) logger.warning(msg) try: course_id = CourseKey.from_string(course_key) except InvalidKeyError: course_id = None msg = 'Invalid course id {course_id}, skipping un-enrollement for {username}, {email}'.format( **row) logger.warning(msg) if user and course_id: enrollment = CourseEnrollment.get_enrollment( user, course_id) if not enrollment: msg = 'Enrollment for the user {} in course {} does not exist!'.format( username, course_key) logger.info(msg) else: try: CourseEnrollment.unenroll(user, course_id, skip_refund=True) except Exception as err: msg = 'Error un-enrolling User {} from course {}: '.format( username, course_key, err) logger.error(msg, exc_info=True)
def get_user_course_expiration_date(user, course): """ Return expiration date for given user course pair. Return None if the course does not expire. Business Logic: - Course access duration is bounded by the min and max duration. - If course fields are missing, default course access duration to MIN_DURATION. """ access_duration = get_user_course_duration(user, course) if access_duration is None: return None enrollment = CourseEnrollment.get_enrollment(user, course.id) if enrollment is None or enrollment.mode != CourseMode.AUDIT: return None try: # Content availability date is equivalent to max(enrollment date, course start date) # for most people. Using the schedule date will provide flexibility to deal with # more complex business rules in the future. content_availability_date = enrollment.schedule.start_date # We have anecdotally observed a case where the schedule.start_date was # equal to the course start, but should have been equal to the enrollment start # https://openedx.atlassian.net/browse/PROD-58 # This section is meant to address that case if enrollment.created and course.start: if (content_availability_date.date() == course.start.date() and course.start < enrollment.created < timezone.now()): content_availability_date = enrollment.created # If course teams change the course start date, set the content_availability_date # to max of enrollment or course start date elif (content_availability_date.date() < course.start.date() and content_availability_date.date() < enrollment.created.date()): content_availability_date = max(enrollment.created, course.start) except CourseEnrollment.schedule.RelatedObjectDoesNotExist: content_availability_date = max(enrollment.created, course.start) return content_availability_date + access_duration
def enabled_for_enrollment(cls, enrollment=None, user=None, course_key=None): """ Return whether Course Duration Limits are enabled for this enrollment. Course Duration Limits are enabled for an enrollment if they are enabled for the course being enrolled in (either specifically, or via a containing context, such as the org, site, or globally), and if the configuration is specified to be ``enabled_as_of`` before the enrollment was created. Only one of enrollment and (user, course_key) may be specified at a time. Arguments: enrollment: The enrollment being queried. user: The user being queried. course_key: The CourseKey of the course being queried. """ if CONTENT_TYPE_GATING_FLAG.is_enabled(): return True if enrollment is not None and (user is not None or course_key is not None): raise ValueError('Specify enrollment or user/course_key, but not both') if enrollment is None and (user is None or course_key is None): raise ValueError('Both user and course_key must be specified if no enrollment is provided') if enrollment is None and user is None and course_key is None: raise ValueError('At least one of enrollment or user and course_key must be specified') if course_key is None: course_key = enrollment.course_id if enrollment is None: enrollment = CourseEnrollment.get_enrollment(user, course_key) # enrollment might be None if the user isn't enrolled. In that case, # return enablement as if the user enrolled today if enrollment is None: return cls.enabled_for_course(course_key=course_key, target_date=datetime.utcnow().date()) else: current_config = cls.current(course_key=enrollment.course_id) return current_config.enabled_as_of_date(target_date=enrollment.created.date())
def is_enabled(cls, request, course_key): """ Show this link for active courses where financial assistance is available, unless upgrade deadline has passed """ now = datetime.datetime.now(pytz.UTC) feature_flags = None try: course_overview = CourseOverview.objects.get(id=course_key) except CourseOverview.DoesNotExist: course_overview = None # hide link if there's no ENABLE_FINANCIAL_ASSISTANCE_FORM setting (ex: Edge) or if it's False subset_name = 'FEATURES' feature_flags = getattr(settings, subset_name) if feature_flags is None or not feature_flags.get( 'ENABLE_FINANCIAL_ASSISTANCE_FORM'): return False # hide link for archived courses if course_overview is not None and course_overview.end_date is not None and now > course_overview.end_date: return False # hide link if not logged in or user not enrolled in the course if not request.user or not CourseEnrollment.is_enrolled( request.user, course_key): return False enrollment = CourseEnrollment.get_enrollment(request.user, course_key) # hide if we're no longer in an upsell mode (already upgraded) if enrollment.mode not in CourseMode.UPSELL_TO_VERIFIED_MODES: return False # hide if there's no course_upgrade_deadline, or one with a value in the past if enrollment.course_upgrade_deadline: if now > enrollment.course_upgrade_deadline: return False else: return False return bool(course_overview.eligible_for_financial_aid)
def get_user_course_expiration_date(user, course): """ Return expiration date for given user course pair. Return None if the course does not expire. Business Logic: - Course access duration is bounded by the min and max duration. - If course fields are missing, default course access duration to MIN_DURATION. """ access_duration = MIN_DURATION verified_mode = CourseMode.verified_mode_for_course(course=course, include_expired=True) if not verified_mode: return None enrollment = CourseEnrollment.get_enrollment(user, course.id) if enrollment is None or enrollment.mode != CourseMode.AUDIT: return None try: # Content availability date is equivalent to max(enrollment date, course start date) # for most people. Using the schedule date will provide flexibility to deal with # more complex business rules in the future. content_availability_date = enrollment.schedule.start except CourseEnrollment.schedule.RelatedObjectDoesNotExist: content_availability_date = max(enrollment.created, course.start) # The user course expiration date is the content availability date # plus the weeks_to_complete field from course-discovery. discovery_course_details = get_course_run_details(course.id, ['weeks_to_complete']) expected_weeks = discovery_course_details.get('weeks_to_complete') if expected_weeks: access_duration = timedelta(weeks=expected_weeks) # Course access duration is bounded by the min and max duration. access_duration = max(MIN_DURATION, min(MAX_DURATION, access_duration)) return content_availability_date + access_duration
def postpay_callback(request): """ Receives the POST-back from processor. Mainly this calls the processor-specific code to check if the payment was accepted, and to record the order if it was, and to generate an error page. If successful this function should have the side effect of changing the "cart" into a full "order" in the DB. The cart can then render a success page which links to receipt pages. If unsuccessful the order will be left untouched and HTML messages giving more detailed error info will be returned. """ params = request.POST.dict() result = process_postpay_callback(params) if result["success"]: # See if this payment occurred as part of the verification flow process # If so, send the user back into the flow so they have the option # to continue with verification. # Only orders where order_items.count() == 1 might be attempting to upgrade attempting_upgrade = request.session.get("attempting_upgrade", False) if attempting_upgrade: if result["order"].has_items(CertificateItem): course_id = result["order"].orderitem_set.all().select_subclasses("certificateitem")[0].course_id if course_id: course_enrollment = CourseEnrollment.get_enrollment(request.user, course_id) if course_enrollment: course_enrollment.emit_event(EVENT_NAME_USER_UPGRADED) request.session["attempting_upgrade"] = False verify_flow_redirect = _get_verify_flow_redirect(result["order"]) if verify_flow_redirect is not None: return verify_flow_redirect # Otherwise, send the user to the receipt page return HttpResponseRedirect(reverse("shoppingcart.views.show_receipt", args=[result["order"].id])) else: request.session["attempting_upgrade"] = False return render_to_response( "shoppingcart/error.html", {"order": result["order"], "error_html": result["error_html"]} )
def __init__(self, course_key, request, username=''): self.overview = course_detail( request, username or request.user.username, course_key, ) self.effective_user = self.overview.effective_user # We need to memoize `is_staff` _before_ we configure masquerade. self.is_staff = has_access(self.effective_user, 'staff', self.overview).has_access self.course_key = course_key self.enrollment_object = CourseEnrollment.get_enrollment( self.effective_user, self.course_key, select_related=['celebration']) course_masquerade, _user = setup_masquerade( request, course_key, staff_access=self.is_staff, ) self.course_masquerade = course_masquerade
def postpay_callback(request): """ Receives the POST-back from processor. Mainly this calls the processor-specific code to check if the payment was accepted, and to record the order if it was, and to generate an error page. If successful this function should have the side effect of changing the "cart" into a full "order" in the DB. The cart can then render a success page which links to receipt pages. If unsuccessful the order will be left untouched and HTML messages giving more detailed error info will be returned. """ params = request.POST.dict() result = process_postpay_callback(params) if result['success']: # See if this payment occurred as part of the verification flow process # If so, send the user back into the flow so they have the option # to continue with verification. # Only orders where order_items.count() == 1 might be attempting to upgrade attempting_upgrade = request.session.get('attempting_upgrade', False) if attempting_upgrade: if result['order'].has_items(CertificateItem): course_id = result['order'].orderitem_set.all().select_subclasses("certificateitem")[0].course_id if course_id: course_enrollment = CourseEnrollment.get_enrollment(request.user, course_id) if course_enrollment: course_enrollment.emit_event(EVENT_NAME_USER_UPGRADED) request.session['attempting_upgrade'] = False verify_flow_redirect = _get_verify_flow_redirect(result['order']) if verify_flow_redirect is not None: return verify_flow_redirect # Otherwise, send the user to the receipt page return HttpResponseRedirect(reverse('shoppingcart.views.show_receipt', args=[result['order'].id])) else: request.session['attempting_upgrade'] = False return render_to_response('shoppingcart/error.html', {'order': result['order'], 'error_html': result['error_html']})
def _enroll_entitlement(self, entitlement, course_run_key, user): """ Internal method to handle the details of enrolling a User in a Course Run. Returns a response object is there is an error or exception, None otherwise """ try: enrollment = CourseEnrollment.enroll( user=user, course_key=course_run_key, mode=entitlement.mode, check_access=True ) except AlreadyEnrolledError: enrollment = CourseEnrollment.get_enrollment(user, course_run_key) if enrollment.mode == entitlement.mode: entitlement.set_enrollment(enrollment) elif enrollment.mode not in [mode.slug for mode in CourseMode.paid_modes_for_course(course_run_key)]: enrollment.update_enrollment(mode=entitlement.mode) entitlement.set_enrollment(enrollment) # Else the User is already enrolled in another paid Mode and we should # not do anything else related to Entitlements. except CourseEnrollmentException: message = ( 'Course Entitlement Enroll for {username} failed for course: {course_id}, ' 'mode: {mode}, and entitlement: {entitlement}' ).format( username=user.username, course_id=course_run_key, mode=entitlement.mode, entitlement=entitlement.uuid ) return Response( status=status.HTTP_400_BAD_REQUEST, data={'message': message} ) entitlement.set_enrollment(enrollment) return None
def get_expiration_banner_text(user, course): """ Get text for banner that messages user course expiration date for different tests that depend on it. """ expiration_date = (now() + timedelta(weeks=4)).strftime('%b. %-d, %Y') upgrade_link = verified_upgrade_deadline_link(user=user, course=course) enrollment = CourseEnrollment.get_enrollment(user, course.id) upgrade_deadline = enrollment.upgrade_deadline if upgrade_deadline is None: return if now() < upgrade_deadline: upgrade_deadline = enrollment.course_upgrade_deadline bannerText = '<strong>Audit Access Expires {expiration_date}</strong><br>\ You lose all access to this course, including your progress, on {expiration_date}.<br>\ Upgrade by {upgrade_deadline} to get unlimited access to the course as long as it exists on the site.\ <a href="{upgrade_link}">Upgrade now<span class="sr-only"> to retain access past {expiration_date}\ </span></a>'.format( expiration_date=expiration_date, upgrade_link=upgrade_link, upgrade_deadline=upgrade_deadline.strftime('%b. %-d, %Y')) return bannerText
def _enroll_entitlement(self, entitlement, course_run_key, user): """ Internal method to handle the details of enrolling a User in a Course Run. Returns a response object is there is an error or exception, None otherwise """ try: enrollment = CourseEnrollment.enroll( user=user, course_key=course_run_key, mode=entitlement.mode, check_access=True ) except AlreadyEnrolledError: enrollment = CourseEnrollment.get_enrollment(user, course_run_key) if enrollment.mode == entitlement.mode: entitlement.set_enrollment(enrollment) elif enrollment.mode not in [mode.slug for mode in CourseMode.paid_modes_for_course(course_run_key)]: enrollment.update_enrollment(mode=entitlement.mode) entitlement.set_enrollment(enrollment) # Else the User is already enrolled in another paid Mode and we should # not do anything else related to Entitlements. except CourseEnrollmentException: message = ( 'Course Entitlement Enroll for {username} failed for course: {course_id}, ' 'mode: {mode}, and entitlement: {entitlement}' ).format( username=user.username, course_id=course_run_key, mode=entitlement.mode, entitlement=entitlement.uuid ) return Response( status=status.HTTP_400_BAD_REQUEST, data={'message': message} ) entitlement.set_enrollment(enrollment) return None
def _is_enrollment_inside_date_bounds(self, experiment_values, user, course_key): """ Returns True if the user's enrollment (if any) is valid for the configured experiment date range """ from student.models import CourseEnrollment enrollment_start = experiment_values.get('enrollment_start') enrollment_end = experiment_values.get('enrollment_end') if not enrollment_start and not enrollment_end: return True # early exit just to avoid any further lookups now = datetime.datetime.now(pytz.utc) enrollment = CourseEnrollment.get_enrollment(user, course_key) # If the user isn't enrolled, act like they would enroll right now (this keeps the pre-enroll and post-enroll # experiences the same, if they decide to enroll right now) enrollment_creation_date = enrollment.created if enrollment else now # Enrollment must be after any enrollment_start date, if specified if enrollment_start: try: start_date = dateutil.parser.parse(enrollment_start).replace(tzinfo=pytz.UTC) except ValueError: log.exception('Could not parse enrollment start date for experiment %d', self.experiment_id) return False if enrollment_creation_date < start_date: return False # Enrollment must be before any enrollment_end date, if specified if enrollment_end: try: end_date = dateutil.parser.parse(enrollment_end).replace(tzinfo=pytz.UTC) except ValueError: log.exception('Could not parse enrollment end date for experiment %d', self.experiment_id) return False if enrollment_creation_date >= end_date: return False # All good! Either because the key was not set or because the enrollment was valid return True
def check_and_get_upgrade_link_and_date(user, enrollment=None, course=None): """ For an authenticated user, return a link to allow them to upgrade in the specified course. Returns the upgrade link and upgrade deadline for a user in a given course given that the user is within the window to upgrade defined by our dynamic pacing feature; otherwise, returns None for both the link and date. """ if enrollment is None and course is None: logger.warn(u'Must specify either an enrollment or a course') return (None, None) if enrollment: if course is None: course = enrollment.course elif enrollment.course_id != course.id: logger.warn(u'{} refers to a different course than {} which was supplied'.format( enrollment, course )) return (None, None) if enrollment.user_id != user.id: logger.warn(u'{} refers to a different user than {} which was supplied'.format( enrollment, user )) return (None, None) if enrollment is None: enrollment = CourseEnrollment.get_enrollment(user, course.id) if user.is_authenticated and verified_upgrade_link_is_valid(enrollment): return ( verified_upgrade_deadline_link(user, course), enrollment.upgrade_deadline ) return (None, None)
def get_user_course_expiration_date(user, course): """ Return expiration date for given user course pair. Return None if the course does not expire. Business Logic: - Course access duration is bounded by the min and max duration. - If course fields are missing, default course access duration to MIN_DURATION. """ access_duration = get_user_course_duration(user, course) if access_duration is None: return None enrollment = CourseEnrollment.get_enrollment(user, course.id) if enrollment is None or enrollment.mode != CourseMode.AUDIT: return None # We reset schedule.start in order to change a user's computed deadlines. # But their expiration date shouldn't change when we adjust their schedule (they don't # get additional time), so we need to based the expiration date on a fixed start date. content_availability_date = max(enrollment.created, course.start) return content_availability_date + access_duration
def get_enrollment_attributes(user_id, course_id): """Retrieve enrollment attributes for given user for provided course. Args: user_id: The User to get enrollment attributes for course_id (str): The Course to get enrollment attributes for. Example: >>>get_enrollment_attributes("Bob", "course-v1-edX-DemoX-1T2015") [ { "namespace": "credit", "name": "provider_id", "value": "hogwarts", }, ] Returns: list """ course_key = CourseKey.from_string(course_id) user = _get_user(user_id) enrollment = CourseEnrollment.get_enrollment(user, course_key) return CourseEnrollmentAttribute.get_enrollment_attributes(enrollment)
def get_enrollment_attributes(user_id, course_id): """Retrieve enrollment attributes for given user for provided course. Args: user_id: The User to get enrollment attributes for course_id (str): The Course to get enrollment attributes for. Example: >>>get_enrollment_attributes("Bob", "course-v1-edX-DemoX-1T2015") [ { "namespace": "credit", "name": "provider_id", "value": "hogwarts", }, ] Returns: list """ course_key = CourseKey.from_string(course_id) user = _get_user(user_id) enrollment = CourseEnrollment.get_enrollment(user, course_key) return CourseEnrollmentAttribute.get_enrollment_attributes(enrollment)
def calculate_course_home(course_id, user): """ Calculate course home. """ course_key = CourseKey.from_string(course_id) user_is_enrolled = CourseEnrollment.is_enrolled(user, course_key) if not user_is_enrolled: return None course = get_course_by_id(course_key) custom_course_settings = course.other_course_settings course_entry_points = custom_course_settings.get("course_entry_points", []) enrollment = CourseEnrollment.get_enrollment(user, course_key) custom_entry_point = _calculate_entry_point(course, course_id, course_key, course_entry_points, user, enrollment) if custom_entry_point: return custom_entry_point if is_external_course(course_id): return custom_course_settings.get("external_course_target") return None
def is_enabled(cls, request, course_key): """ Show this tool to all learners who are eligible to upgrade. """ enrollment = CourseEnrollment.get_enrollment(request.user, course_key) if enrollment is None: return False if enrollment.dynamic_upgrade_deadline is None: return False if not enrollment.is_active: return False if enrollment.mode not in CourseMode.UPSELL_TO_VERIFIED_MODES: return False if enrollment.course_upgrade_deadline is None: return False if datetime.datetime.now(pytz.UTC) >= enrollment.course_upgrade_deadline: return False return True
def register_alerts(self, request, course): """ Registers an alert close to the certificate delivery date. """ is_enrolled = CourseEnrollment.get_enrollment(request.user, course.id) if not is_enrolled or not self.is_enabled or course.end > self.current_time: return if self.date > self.current_time: CourseHomeMessages.register_info_message( request, Text(_( u'If you have earned a certificate, you will be able to access it {time_remaining_string}' u' from now. You will also be able to view your certificates on your {learner_profile_link}.' )).format( time_remaining_string=self.time_remaining_string, learner_profile_link=HTML( u'<a href="{learner_profile_url}">{learner_profile_name}</a>' ).format( learner_profile_url=reverse('learner_profile', kwargs={'username': request.user.username}), learner_profile_name=_('Learner Profile'), ), ), title=Text(_('We are working on generating course certificates.')) )
def _get_zendesk_custom_field_context(request, **kwargs): """ Construct a dictionary of data that can be stored in Zendesk custom fields. """ context = {} course_id = request.POST.get("course_id") if not course_id: return context context["course_id"] = course_id if not request.user.is_authenticated(): return context enrollment = CourseEnrollment.get_enrollment(request.user, CourseKey.from_string(course_id)) if enrollment and enrollment.is_active: context["enrollment_mode"] = enrollment.mode enterprise_learner_data = kwargs.get('learner_data', None) if enterprise_learner_data: enterprise_customer_name = enterprise_learner_data[0]['enterprise_customer']['name'] context["enterprise_customer_name"] = enterprise_customer_name return context
def post(self, request, course_key_string, *args, **kwargs): """ Handle a POST request. """ course_key = CourseKey.from_string(course_key_string) # Check if we're masquerading as someone else. If so, we should just ignore this request. _, user = setup_masquerade( request, course_key, staff_access=has_access(request.user, 'staff', course_key), reset_masquerade_data=True, ) if user != request.user: return Response(status=202) # "Accepted" data = dict(request.data) first_section = data.pop('first_section', None) if data: return Response( status=400) # there were parameters we didn't recognize enrollment = CourseEnrollment.get_enrollment(request.user, course_key) if not enrollment: return Response(status=404) defaults = {} if first_section is not None: defaults['celebrate_first_section'] = first_section if defaults: _, created = CourseEnrollmentCelebration.objects.update_or_create( enrollment=enrollment, defaults=defaults) return Response(status=201 if created else 200) else: return Response(status=200) # just silently allow it