def _get_recipient_queryset(user_id, to_option, course_id, course_location): """ Returns a query set of email recipients corresponding to the requested to_option category. `to_option` is either SEND_TO_MYSELF, SEND_TO_STAFF, or SEND_TO_ALL. Recipients who are in more than one category (e.g. enrolled in the course and are staff or self) will be properly deduped. """ if to_option not in TO_OPTIONS: log.error("Unexpected bulk email TO_OPTION found: %s", to_option) raise Exception("Unexpected bulk email TO_OPTION found: {0}".format(to_option)) if to_option == SEND_TO_MYSELF: recipient_qset = User.objects.filter(id=user_id) else: staff_grpname = _course_staff_group_name(course_location) staff_group, _ = Group.objects.get_or_create(name=staff_grpname) staff_qset = staff_group.user_set.all() instructor_grpname = _course_instructor_group_name(course_location) instructor_group, _ = Group.objects.get_or_create(name=instructor_grpname) instructor_qset = instructor_group.user_set.all() recipient_qset = staff_qset | instructor_qset if to_option == SEND_TO_ALL: # We also require students to have activated their accounts to # provide verification that the provided email address is valid. enrollment_qset = User.objects.filter( is_active=True, courseenrollment__course_id=course_id, courseenrollment__is_active=True ) recipient_qset = recipient_qset | enrollment_qset recipient_qset = recipient_qset.distinct() recipient_qset = recipient_qset.order_by('pk') return recipient_qset
def delegate_email_batches(email_id, user_id): """ Delegates emails by querying for the list of recipients who should get the mail, chopping up into batches of settings.EMAILS_PER_TASK size, and queueing up worker jobs. Returns the number of batches (workers) kicked off. """ try: email_obj = CourseEmail.objects.get(id=email_id) except CourseEmail.DoesNotExist as exc: # The retry behavior here is necessary because of a race condition between the commit of the transaction # that creates this CourseEmail row and the celery pipeline that starts this task. # We might possibly want to move the blocking into the view function rather than have it in this task. log.warning("Failed to get CourseEmail with id %s, retry %d", email_id, current_task.request.retries) raise delegate_email_batches.retry(arg=[email_id, user_id], exc=exc) to_option = email_obj.to_option course_id = email_obj.course_id try: course = get_course_by_id(course_id, depth=1) except Http404 as exc: log.exception("get_course_by_id failed: %s", exc.args[0]) raise Exception("get_course_by_id failed: " + exc.args[0]) course_url = 'https://{}{}'.format( settings.SITE_NAME, reverse('course_root', kwargs={'course_id': course_id}) ) image_url = 'https://{}{}'.format(settings.SITE_NAME, course_image_url(course)) if to_option == SEND_TO_MYSELF: recipient_qset = User.objects.filter(id=user_id) elif to_option == SEND_TO_ALL or to_option == SEND_TO_STAFF: staff_grpname = _course_staff_group_name(course.location) staff_group, _ = Group.objects.get_or_create(name=staff_grpname) staff_qset = staff_group.user_set.all() instructor_grpname = _course_instructor_group_name(course.location) instructor_group, _ = Group.objects.get_or_create(name=instructor_grpname) instructor_qset = instructor_group.user_set.all() recipient_qset = staff_qset | instructor_qset if to_option == SEND_TO_ALL: enrollment_qset = User.objects.filter(courseenrollment__course_id=course_id, courseenrollment__is_active=True) recipient_qset = recipient_qset | enrollment_qset recipient_qset = recipient_qset.distinct() else: log.error("Unexpected bulk email TO_OPTION found: %s", to_option) raise Exception("Unexpected bulk email TO_OPTION found: {0}".format(to_option)) recipient_qset = recipient_qset.order_by('pk') total_num_emails = recipient_qset.count() num_queries = int(math.ceil(float(total_num_emails) / float(settings.EMAILS_PER_QUERY))) last_pk = recipient_qset[0].pk - 1 num_workers = 0 for _ in range(num_queries): recipient_sublist = list(recipient_qset.order_by('pk').filter(pk__gt=last_pk) .values('profile__name', 'email', 'pk')[:settings.EMAILS_PER_QUERY]) last_pk = recipient_sublist[-1]['pk'] num_emails_this_query = len(recipient_sublist) num_tasks_this_query = int(math.ceil(float(num_emails_this_query) / float(settings.EMAILS_PER_TASK))) chunk = int(math.ceil(float(num_emails_this_query) / float(num_tasks_this_query))) for i in range(num_tasks_this_query): to_list = recipient_sublist[i * chunk:i * chunk + chunk] course_email.delay( email_id, to_list, course.display_name, course_url, image_url, False ) num_workers += num_tasks_this_query return num_workers
def delegate_email_batches(email_id, user_id): """ Delegates emails by querying for the list of recipients who should get the mail, chopping up into batches of settings.EMAILS_PER_TASK size, and queueing up worker jobs. Returns the number of batches (workers) kicked off. """ try: email_obj = CourseEmail.objects.get(id=email_id) except CourseEmail.DoesNotExist as exc: # The retry behavior here is necessary because of a race condition between the commit of the transaction # that creates this CourseEmail row and the celery pipeline that starts this task. # We might possibly want to move the blocking into the view function rather than have it in this task. log.warning("Failed to get CourseEmail with id %s, retry %d", email_id, current_task.request.retries) raise delegate_email_batches.retry(arg=[email_id, user_id], exc=exc) to_option = email_obj.to_option course_id = email_obj.course_id try: course = get_course_by_id(course_id, depth=1) except Http404 as exc: log.exception("get_course_by_id failed: %s", exc.args[0]) raise Exception("get_course_by_id failed: " + exc.args[0]) course_url = 'https://{}{}'.format( settings.SITE_NAME, reverse('course_root', kwargs={'course_id': course_id})) image_url = 'https://{}{}'.format(settings.SITE_NAME, course_image_url(course)) if to_option == SEND_TO_MYSELF: recipient_qset = User.objects.filter(id=user_id) elif to_option == SEND_TO_ALL or to_option == SEND_TO_STAFF: staff_grpname = _course_staff_group_name(course.location) staff_group, _ = Group.objects.get_or_create(name=staff_grpname) staff_qset = staff_group.user_set.all() instructor_grpname = _course_instructor_group_name(course.location) instructor_group, _ = Group.objects.get_or_create( name=instructor_grpname) instructor_qset = instructor_group.user_set.all() recipient_qset = staff_qset | instructor_qset if to_option == SEND_TO_ALL: enrollment_qset = User.objects.filter( courseenrollment__course_id=course_id, courseenrollment__is_active=True) recipient_qset = recipient_qset | enrollment_qset recipient_qset = recipient_qset.distinct() else: log.error("Unexpected bulk email TO_OPTION found: %s", to_option) raise Exception( "Unexpected bulk email TO_OPTION found: {0}".format(to_option)) recipient_qset = recipient_qset.order_by('pk') total_num_emails = recipient_qset.count() num_queries = int( math.ceil(float(total_num_emails) / float(settings.EMAILS_PER_QUERY))) last_pk = recipient_qset[0].pk - 1 num_workers = 0 for _ in range(num_queries): recipient_sublist = list( recipient_qset.order_by('pk').filter(pk__gt=last_pk).values( 'profile__name', 'email', 'pk')[:settings.EMAILS_PER_QUERY]) last_pk = recipient_sublist[-1]['pk'] num_emails_this_query = len(recipient_sublist) num_tasks_this_query = int( math.ceil( float(num_emails_this_query) / float(settings.EMAILS_PER_TASK))) chunk = int( math.ceil( float(num_emails_this_query) / float(num_tasks_this_query))) for i in range(num_tasks_this_query): to_list = recipient_sublist[i * chunk:i * chunk + chunk] course_email.delay(email_id, to_list, course.display_name, course_url, image_url, False) num_workers += num_tasks_this_query return num_workers