def get_users(self, course_id, user_id=None):
        """
        Gets the users for a given target.

        Result is returned in the form of a queryset, and may contain duplicates.
        """
        staff_qset = CourseStaffRole(course_id).users_with_role()
        instructor_qset = CourseInstructorRole(course_id).users_with_role()
        staff_instructor_qset = (staff_qset | instructor_qset)
        enrollment_query = models.Q(is_active=True,
                                    courseenrollment__course_id=course_id,
                                    courseenrollment__is_active=True)
        enrollment_qset = User.objects.filter(enrollment_query)
        if self.target_type == SEND_TO_MYSELF:
            if user_id is None:
                raise ValueError(
                    "Must define self user to send email to self.")
            user = User.objects.filter(id=user_id)
            return use_read_replica_if_available(user)
        elif self.target_type == SEND_TO_STAFF:
            return use_read_replica_if_available(staff_instructor_qset)
        elif self.target_type == SEND_TO_LEARNERS:
            return use_read_replica_if_available(
                enrollment_qset.exclude(id__in=staff_instructor_qset))
        elif self.target_type == SEND_TO_COHORT:
            return self.cohorttarget.cohort.users.filter(
                id__in=enrollment_qset)  # lint-amnesty, pylint: disable=no-member
        elif self.target_type == SEND_TO_TRACK:
            return use_read_replica_if_available(
                User.objects.filter(
                    models.Q(courseenrollment__mode=self.coursemodetarget.
                             track.mode_slug)
                    & enrollment_query))
        else:
            raise ValueError(f"Unrecognized target type {self.target_type}")
Пример #2
0
    def _paginate_users(self,
                        course_key,
                        course_enrollment_filter=None,
                        related_models=None,
                        annotations=None):
        """
        Args:
            course_key (CourseLocator): The course to retrieve grades for.
            course_enrollment_filter: Optional list of Q objects to pass
            to `CourseEnrollment.filter()`.
            related_models: Optional list of related models to join to the CourseEnrollment table.
            annotations: Optional dict of fields to add to the queryset via annotation

        Returns:
            A list of users, pulled from a paginated queryset of enrollments, who are enrolled in the given course.
        """
        queryset = CourseEnrollment.objects
        if annotations:
            queryset = queryset.annotate(**annotations)

        filter_args = [Q(course_id=course_key) & Q(is_active=True)]
        filter_args.extend(course_enrollment_filter or [])

        enrollments_in_course = use_read_replica_if_available(
            queryset.filter(*filter_args))
        if related_models:
            enrollments_in_course = enrollments_in_course.select_related(
                *related_models)

        paged_enrollments = self.paginate_queryset(enrollments_in_course)
        retlist = []
        for enrollment in paged_enrollments:
            enrollment.user.enrollment_mode = enrollment.mode
            retlist.append(enrollment.user)
        return retlist
Пример #3
0
    def handle(self, *args, **options):
        """
        Handler for the command
        It filters approved Software Secure Photo Verifications and then sets the correct expiration_date
        """
        batch_size = options['batch_size']
        sleep_time = options['sleep_time']

        query = SoftwareSecurePhotoVerification.objects.filter(status='approved').order_by()
        sspv = use_read_replica_if_available(query)

        if not sspv.count():
            logger.info("No approved entries found in SoftwareSecurePhotoVerification")
            return

        update_verification_ids = []
        update_verification_count = 0

        for verification in sspv:
            # The expiration date should not be higher than 365 days
            # past the `updated_at` field, so only update those entries
            if verification.expiration_date > (
                verification.updated_at + timedelta(days=settings.VERIFY_STUDENT["DAYS_GOOD_FOR"])
            ):
                update_verification_ids.append(verification.pk)
                update_verification_count += 1

            if update_verification_count == batch_size:
                self.bulk_update(update_verification_ids)
                update_verification_count = 0
                update_verification_ids = []
                time.sleep(sleep_time)

        if update_verification_ids:
            self.bulk_update(update_verification_ids)
Пример #4
0
    def get_users(self, course_id, user_id=None):
        """
        Gets the users for a given target.

        Result is returned in the form of a queryset, and may contain duplicates.
        """
        staff_qset = CourseStaffRole(course_id).users_with_role()
        instructor_qset = CourseInstructorRole(course_id).users_with_role()
        staff_instructor_qset = (staff_qset | instructor_qset)
        enrollment_query = models.Q(is_active=True,
                                    courseenrollment__course_id=course_id,
                                    courseenrollment__is_active=True)
        enrollment_qset = User.objects.filter(enrollment_query)

        # filter out learners from the message who are no longer active in the course-run based on last login
        last_login_eligibility_period = settings.BULK_COURSE_EMAIL_LAST_LOGIN_ELIGIBILITY_PERIOD
        if last_login_eligibility_period and isinstance(
                last_login_eligibility_period, int):
            cutoff = datetime.now() - relativedelta(
                months=last_login_eligibility_period)
            enrollment_qset = enrollment_qset.exclude(last_login__lte=cutoff)

        if self.target_type == SEND_TO_MYSELF:
            if user_id is None:
                raise ValueError(
                    "Must define self user to send email to self.")
            user = User.objects.filter(id=user_id)
            return use_read_replica_if_available(user)
        elif self.target_type == SEND_TO_STAFF:
            return use_read_replica_if_available(staff_instructor_qset)
        elif self.target_type == SEND_TO_LEARNERS:
            return use_read_replica_if_available(
                enrollment_qset.exclude(id__in=staff_instructor_qset))
        elif self.target_type == SEND_TO_COHORT:
            return self.cohorttarget.cohort.users.filter(
                id__in=enrollment_qset)  # lint-amnesty, pylint: disable=no-member
        elif self.target_type == SEND_TO_TRACK:
            return use_read_replica_if_available(
                User.objects.filter(
                    models.Q(courseenrollment__mode=self.coursemodetarget.
                             track.mode_slug)
                    & enrollment_query))
        else:
            raise ValueError(f"Unrecognized target type {self.target_type}")
    def handle(self, *args, **options):
        """
        Handle the send save for later reminder emails.
        """

        reminder_email_threshold_date = datetime.now() - timedelta(
            days=settings.SAVE_FOR_LATER_REMINDER_EMAIL_THRESHOLD)
        saved_courses_ids = SavedCourse.objects.filter(
            reminder_email_sent=False,
            modified__lt=reminder_email_threshold_date).values_list('id',
                                                                    flat=True)
        total = saved_courses_ids.count()
        batch_size = max(1, options.get('batch_size'))
        num_batches = ((total - 1) / batch_size + 1) if total > 0 else 0

        for batch_num in range(int(num_batches)):
            reminder_email_sent_ids = []
            start = batch_num * batch_size
            end = min(start + batch_size, total) - 1
            saved_courses_batch_ids = list(saved_courses_ids)[start:end + 1]

            query = SavedCourse.objects.filter(
                id__in=saved_courses_batch_ids).order_by('-modified')
            saved_courses = use_read_replica_if_available(query)
            for saved_course in saved_courses:
                user = User.objects.filter(email=saved_course.email).first()
                course_overview = CourseOverview.get_from_id(
                    saved_course.course_id)
                course_data = {
                    'course': course_overview,
                    'send_to_self': None,
                    'user_id': saved_course.user_id,
                    'org_img_url': saved_course.org_img_url,
                    'marketing_url': saved_course.marketing_url,
                    'weeks_to_complete': saved_course.weeks_to_complete,
                    'min_effort': saved_course.min_effort,
                    'max_effort': saved_course.max_effort,
                    'type': 'course',
                    'reminder': True,
                    'braze_event': USER_SEND_SAVE_FOR_LATER_REMINDER_EMAIL,
                }
                if user:
                    enrollment = CourseEnrollment.get_enrollment(
                        user, saved_course.course_id)
                    if enrollment:
                        continue
                email_sent = send_email(saved_course.email, course_data)
                if email_sent:
                    reminder_email_sent_ids.append(saved_course.id)
                else:
                    logging.info(
                        "Unable to send reminder email to {user} for {course} course"
                        .format(user=str(saved_course.email),
                                course=str(saved_course.course_id)))
            SavedCourse.objects.filter(id__in=reminder_email_sent_ids).update(
                reminder_email_sent=True)
 def _get_users_queryset(self, initial_days):
     """
     initial_days: numbers of days to go back from today
     :return: users queryset
     """
     start_date = datetime.now().date() - timedelta(initial_days)
     end_date = datetime.now().date() - timedelta(1)
     self.stdout.write(f'Getting users from {start_date} to {end_date}')
     users_qs = User.objects.filter(
         date_joined__date__gte=start_date,
         date_joined__date__lte=end_date).order_by('id')
     return use_read_replica_if_available(users_qs)
Пример #7
0
    def handle(self, *args, **options):
        """
        Handle the send save for later reminder emails.
        """
        reminder_email_threshold_date = datetime.now() - timedelta(
            days=settings.SAVE_FOR_LATER_REMINDER_EMAIL_THRESHOLD)
        saved_program_ids = SavedProgram.objects.filter(
            reminder_email_sent=False,
            modified__lt=reminder_email_threshold_date).values_list('id',
                                                                    flat=True)
        total = saved_program_ids.count()
        batch_size = max(1, options.get('batch_size'))
        num_batches = ((total - 1) / batch_size + 1) if total > 0 else 0

        for batch_num in range(int(num_batches)):
            reminder_email_sent_ids = []
            start = batch_num * batch_size
            end = min(start + batch_size, total) - 1
            saved_program_batch_ids = list(saved_program_ids)[start:end + 1]

            query = SavedProgram.objects.filter(
                id__in=saved_program_batch_ids).order_by('-modified')
            saved_programs = use_read_replica_if_available(query)
            for saved_program in saved_programs:
                user = User.objects.filter(email=saved_program.email).first()
                program = get_programs(uuid=saved_program.program_uuid)
                if program:
                    program_data = {
                        'program': program,
                        'send_to_self': None,
                        'user_id': saved_program.user_id,
                        'type': 'program',
                        'reminder': True,
                        'braze_event': USER_SEND_SAVE_FOR_LATER_REMINDER_EMAIL,
                    }
                    try:
                        if user and get_program_enrollment(
                                program_uuid=saved_program.program_uuid,
                                user=user):
                            continue
                    except ObjectDoesNotExist:
                        pass
                    email_sent = send_email(saved_program.email, program_data)
                    if email_sent:
                        reminder_email_sent_ids.append(saved_program.id)
                    else:
                        logging.info(
                            "Unable to send reminder email to {user} for {program} program"
                            .format(user=str(saved_program.email),
                                    program=str(saved_program.program_uuid)))
            SavedProgram.objects.filter(id__in=reminder_email_sent_ids).update(
                reminder_email_sent=True)
Пример #8
0
    def _get_enrollments_queryset(self, start_index, end_index):
        """

        Args:
            start_index: start index or None
            end_index:  end index or None

        Returns:
            EnterpriseCourseEnrollments Queryset

        """
        self.stdout.write(
            u'Getting enrollments from {start} to {end} index (as per command params)'
            .format(start=start_index or 'start', end=end_index or 'end'))
        enrollments_qs = EnterpriseCourseEnrollment.objects.filter(
            source__isnull=True).order_by('id')[start_index:end_index]
        return use_read_replica_if_available(enrollments_qs)
    def handle(self, *args, **options):
        """
        Handler for the command
        It filters approved Software Secure Photo Verification and then for each distinct user it finds the most
        recent approved verification and set its expiration_date
        """
        batch_size = options['batch_size']
        sleep_time = options['sleep_time']

        query = SoftwareSecurePhotoVerification.objects.filter(
            status='approved').order_by()
        sspv = use_read_replica_if_available(query)

        if not sspv.count():
            logger.info(
                "No approved entries found in SoftwareSecurePhotoVerification")
            return

        distinct_user_ids = set()
        update_verification_ids = []
        update_verification_count = 0

        for verification in sspv:
            if verification.user_id not in distinct_user_ids:
                distinct_user_ids.add(verification.user_id)

                recent_verification = self.find_recent_verification(
                    sspv, verification.user_id)
                if recent_verification.expiry_date:
                    update_verification_ids.append(recent_verification.pk)
                    update_verification_count += 1

                if update_verification_count == batch_size:
                    self.bulk_update(update_verification_ids)
                    update_verification_count = 0
                    update_verification_ids = []
                    time.sleep(sleep_time)

        if update_verification_ids:
            self.bulk_update(update_verification_ids)
    def handle(self, *args, **options):
        """
        Handler for the command

        It creates batches of expired Software Secure Photo Verification and sends it to send_verification_expiry_email
        that used edx_ace to send email to these learners
        """
        default_emails = settings.VERIFICATION_EXPIRY_EMAIL['DEFAULT_EMAILS']
        resend_days = settings.VERIFICATION_EXPIRY_EMAIL['RESEND_DAYS']
        days = settings.VERIFICATION_EXPIRY_EMAIL['DAYS_RANGE']
        batch_size = options['batch_size']
        sleep_time = options['sleep_time']
        dry_run = options['dry_run']

        if default_emails <= 0:
            raise CommandError(
                u'DEFAULT_EMAILS must be a positive integer. If you do not wish to send emails '
                u'use --dry-run flag instead.')

        end_date = now().replace(hour=0, minute=0, second=0, microsecond=0)
        # If email was sent and user did not re-verify then this date will be used as the criteria for resending email
        date_resend_days_ago = end_date - timedelta(days=resend_days)

        start_date = end_date - timedelta(days=days)

        # Adding an order_by() clause will override the class meta ordering as we don't need ordering here
        query = SoftwareSecurePhotoVerification.objects.filter(
            Q(status='approved') & (
                Q(expiration_date__isnull=False) &
                (Q(expiration_date__gte=start_date,
                   expiration_date__lt=end_date)
                 | Q(expiry_email_date__lte=date_resend_days_ago)) |
                # Account for old entries still using `expiry_date` rather than`expiration_date`
                # (this will be deprecated)
                Q(expiry_date__isnull=False) &
                (Q(expiry_date__gte=start_date, expiry_date__lt=end_date) | Q(
                    expiry_email_date__lte=date_resend_days_ago)))).order_by()

        sspv = use_read_replica_if_available(query)

        total_verification = sspv.count()
        if not total_verification:
            logger.info(
                u"No approved expired entries found in SoftwareSecurePhotoVerification for the "
                u"date range {} - {}".format(start_date.date(),
                                             now().date()))
            return

        logger.info(
            u"For the date range {} - {}, total Software Secure Photo verification filtered are {}"
            .format(start_date.date(),
                    now().date(), total_verification))

        batch_verifications = []
        email_config = {
            'resend_days': resend_days,
            'dry_run': dry_run,
            'default_emails': default_emails
        }

        success = True
        for verification in sspv:
            user = verification.user
            if self.user_has_valid_verification(user):
                continue
            if not verification.expiry_email_date or verification.expiry_email_date <= date_resend_days_ago:
                batch_verifications.append(verification)

                if len(batch_verifications) == batch_size:
                    success = self.send_verification_expiry_email(
                        batch_verifications, email_config) and success
                    time.sleep(sleep_time)
                    batch_verifications = []

        # If selected verification in batch are less than batch_size
        if batch_verifications:
            success = self.send_verification_expiry_email(
                batch_verifications, email_config) and success

        if not success:
            raise CommandError(
                'One or more email attempts failed. Search for "Could not send" above.'
            )