Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ MITx - Theme 101 ] Move invitation(allowed_enroll) emails from edx_ace to Bulk emails templates #154

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion common/djangoapps/student/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,7 @@ def user_post_save_callback(sender, **kwargs):

changed_fields = user._changed_fields

if 'is_active' in changed_fields or 'email' in changed_fields:
if 'is_active' in changed_fields or 'email' in changed_fields or 'date_joined' in changed_fields:
if user.is_active:
ceas = CourseEnrollmentAllowed.for_user(user).filter(auto_enroll=True)

Expand Down
4 changes: 4 additions & 0 deletions common/lib/xmodule/xmodule/modulestore/django.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,10 @@ def send(self, signal_name, **kwargs):
log.info('Sent %s signal to %s with kwargs %s. Response was: %s', signal_name, receiver, kwargs, response)


# to allow easy imports
globals().update({sig.name.upper(): sig for sig in SignalHandler.all_signals()})


def load_function(path):
"""
Load a function by name.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.23 on 2019-11-27 08:53
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('bulk_email', '0006_course_mode_targets'),
]

operations = [
migrations.AlterField(
model_name='courseemail',
name='targets',
field=models.ManyToManyField(blank=True, to='bulk_email.Target'),
),
migrations.AlterField(
model_name='courseemail',
name='to_option',
field=models.CharField(default=b'', max_length=64),
),
]
60 changes: 32 additions & 28 deletions lms/djangoapps/bulk_email/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,8 @@ class Meta(object):

course_id = CourseKeyField(max_length=255, db_index=True)
# to_option is deprecated and unused, but dropping db columns is hard so it's still here for legacy reasons
to_option = models.CharField(max_length=64, choices=[("deprecated", "deprecated")])
targets = models.ManyToManyField(Target)
to_option = models.CharField(max_length=64, default='')
targets = models.ManyToManyField(Target, blank=True)
template_name = models.CharField(null=True, max_length=255)
from_addr = models.CharField(null=True, max_length=255)

Expand All @@ -242,7 +242,7 @@ def __unicode__(self):

@classmethod
def create(
cls, course_id, sender, targets, subject, html_message,
cls, course_id, sender, subject, html_message, targets=None, to_option='',
text_message=None, template_name=None, from_addr=None):
"""
Create an instance of CourseEmail.
Expand All @@ -251,30 +251,31 @@ def create(
if text_message is None:
text_message = html_to_text(html_message)

new_targets = []
for target in targets:
# split target, to handle cohort:cohort_name and track:mode_slug
target_split = target.split(':', 1)
# Ensure our desired target exists
if target_split[0] not in EMAIL_TARGETS:
fmt = 'Course email being sent to unrecognized target: "{target}" for "{course}", subject "{subject}"'
msg = fmt.format(target=target, course=course_id, subject=subject)
raise ValueError(msg)
elif target_split[0] == SEND_TO_COHORT:
# target_split[1] will contain the cohort name
cohort = CohortTarget.ensure_valid_cohort(target_split[1], course_id)
new_target, _ = CohortTarget.objects.get_or_create(target_type=target_split[0], cohort=cohort)
elif target_split[0] == SEND_TO_TRACK:
# target_split[1] contains the desired mode slug
CourseModeTarget.ensure_valid_mode(target_split[1], course_id)

# There could exist multiple CourseModes that match this query, due to differing currency types.
# The currencies do not affect user lookup though, so we can just use the first result.
mode = CourseMode.objects.filter(course_id=course_id, mode_slug=target_split[1])[0]
new_target, _ = CourseModeTarget.objects.get_or_create(target_type=target_split[0], track=mode)
else:
new_target, _ = Target.objects.get_or_create(target_type=target_split[0])
new_targets.append(new_target)
if targets:
new_targets = []
for target in targets:
# split target, to handle cohort:cohort_name and track:mode_slug
target_split = target.split(':', 1)
# Ensure our desired target exists
if target_split[0] not in EMAIL_TARGETS:
fmt = 'Course email being sent to unrecognized target: "{target}" for "{course}", subject "{subject}"'
msg = fmt.format(target=target, course=course_id, subject=subject)
raise ValueError(msg)
elif target_split[0] == SEND_TO_COHORT:
# target_split[1] will contain the cohort name
cohort = CohortTarget.ensure_valid_cohort(target_split[1], course_id)
new_target, _ = CohortTarget.objects.get_or_create(target_type=target_split[0], cohort=cohort)
elif target_split[0] == SEND_TO_TRACK:
# target_split[1] contains the desired mode slug
CourseModeTarget.ensure_valid_mode(target_split[1], course_id)

# There could exist multiple CourseModes that match this query, due to differing currency types.
# The currencies do not affect user lookup though, so we can just use the first result.
mode = CourseMode.objects.filter(course_id=course_id, mode_slug=target_split[1])[0]
new_target, _ = CourseModeTarget.objects.get_or_create(target_type=target_split[0], track=mode)
else:
new_target, _ = Target.objects.get_or_create(target_type=target_split[0])
new_targets.append(new_target)

# create the task, then save it immediately:
course_email = cls(
Expand All @@ -286,8 +287,11 @@ def create(
template_name=template_name,
from_addr=from_addr,
)

course_email.to_option = to_option
course_email.save() # Must exist in db before setting M2M relationship values
course_email.targets.add(*new_targets)
if targets:
course_email.targets.add(*new_targets)
course_email.save()

return course_email
Expand Down
82 changes: 57 additions & 25 deletions lms/djangoapps/bulk_email/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,20 +175,31 @@ def perform_delegate_email_batches(entry_id, course_id, task_input, action_name)
targets = email_obj.targets.all()
global_email_context = _get_course_email_context(course)

recipient_qsets = [
target.get_users(course_id, user_id)
for target in targets
]
combined_set = User.objects.none()
for qset in recipient_qsets:
combined_set |= qset
combined_set = combined_set.distinct()
recipient_fields = ['profile__name', 'email']
if targets:
recipient_qsets = [
target.get_users(course_id, user_id)
for target in targets
]
else:
recipient_qsets = str(email_obj.to_option) # unicode to string
try:
combined_set = User.objects.none()
for qset in recipient_qsets:
combined_set |= qset
combined_set = combined_set.distinct()
recipient_fields = ['profile__name', 'email']
except:
combined_set = recipient_qsets
recipient_fields = ['email']


log.info(u"Task %s: Preparing to queue subtasks for sending emails for course %s, email %s",
task_id, course_id, email_id)

total_recipients = combined_set.count()
try:
total_recipients = combined_set.count()
except Exception as e:
total_recipients = 1

routing_key = settings.BULK_EMAIL_ROUTING_KEY
# if there are few enough emails, send them through a different queue
Expand Down Expand Up @@ -339,16 +350,23 @@ def _filter_optouts_from_recipients(to_list, course_id):
Returns the filtered recipient list, as well as the number of optouts
removed from the list.
"""
optouts = Optout.objects.filter(
course_id=course_id,
user__in=[i['pk'] for i in to_list]
).values_list('user__email', flat=True)
optouts = set(optouts)
# Only count the num_optout for the first time the optouts are calculated.
# We assume that the number will not change on retries, and so we don't need
# to calculate it each time.
num_optout = len(optouts)
to_list = [recipient for recipient in to_list if recipient['email'] not in optouts]
try:
optouts = Optout.objects.filter(
course_id=course_id,
user__in=[i['pk'] for i in to_list]
).values_list('user__email', flat=True)
optouts = set(optouts)
# Only count the num_optout for the first time the optouts are calculated.
# We assume that the number will not change on retries, and so we don't need
# to calculate it each time.
num_optout = len(optouts)
to_list = [recipient for recipient in to_list if recipient['email'] not in optouts]
except KeyError:
user_record = to_list[0]
user_record["pk"] = 0
user_record["profile__name"] = user_record['email'].split("@")[0]
to_list = [user_record]
num_optout = len(to_list)
return to_list, num_optout


Expand Down Expand Up @@ -489,9 +507,16 @@ def _send_course_email(entry_id, email_id, to_list, global_email_context, subtas
course_title = global_email_context['course_title']
course_language = global_email_context['course_language']

# use the email from address in the CourseEmail, if it is present, otherwise compute it
from_addr = course_email.from_addr if course_email.from_addr else \
_get_source_address(course_email.course_id, course_title, course_language)
# If you don't want the from_addr to be dynamically generated, this uses
# the configured default from address if the flag is set to True in settings
if settings.EMAIL_USE_DEFAULT_FROM_FOR_BULK:
from_addr = settings.DEFAULT_FROM_EMAIL
else:
# use the email from address in the CourseEmail, if it is present, otherwise compute it
from_addr = course_email.from_addr if course_email.from_addr else \
_get_source_address(course_email.course_id, course_title, course_language)

from_addr = "{} course team <{}>".format(course_title, from_addr)

# use the CourseEmailTemplate that was associated with the CourseEmail
course_email_template = course_email.get_template()
Expand All @@ -511,6 +536,7 @@ def _send_course_email(entry_id, email_id, to_list, global_email_context, subtas
# yet been emailed, but not send to those who have already been sent to.
recipient_num += 1
current_recipient = to_list[-1]

email = current_recipient['email']
if _has_non_ascii_characters(email):
to_list.pop()
Expand All @@ -526,8 +552,14 @@ def _send_course_email(entry_id, email_id, to_list, global_email_context, subtas
continue

email_context['email'] = email
email_context['name'] = current_recipient['profile__name']
email_context['user_id'] = current_recipient['pk']
try:
email_context['name'] = current_recipient['profile__name']
except KeyError:
email_context['name'] = email.split("@")[0]
try:
email_context['user_id'] = current_recipient['pk']
except KeyError:
email_context['user_id'] = 0
email_context['course_id'] = course_email.course_id

# Construct message content using templates and context:
Expand Down
3 changes: 3 additions & 0 deletions lms/djangoapps/ccx/api/v0/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,7 @@ def post(self, request):
display_name=ccx_course_object.display_name
)
enroll_email(
request=request,
course_id=ccx_course_key,
student_email=coach.email,
auto_enroll=True,
Expand All @@ -511,6 +512,7 @@ def post(self, request):
assign_staff_role_to_ccx(ccx_course_key, coach, master_course_object.id)
# assign staff role for all the staff and instructor of the master course to the newly created ccx
add_master_course_staff_to_ccx(
request=request,
master_course_object,
ccx_course_key,
ccx_course_object.display_name,
Expand Down Expand Up @@ -764,6 +766,7 @@ def patch(self, request, ccx_course_id=None):
display_name=ccx_course_object.display_name
)
enroll_email(
request=request,
course_id=ccx_course_key,
student_email=coach.email,
auto_enroll=True,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def add_master_course_staff_to_ccx_for_existing_ccx(apps, schema_editor):
try:
course = get_course_by_id(ccx.course_id)
add_master_course_staff_to_ccx(
request=None,
course,
ccx_locator,
ccx.display_name,
Expand Down Expand Up @@ -67,6 +68,7 @@ def remove_master_course_staff_from_ccx_for_existing_ccx(apps, schema_editor):
try:
course = get_course_by_id(ccx.course_id)
remove_master_course_staff_from_ccx(
request=None,
course,
ccx_locator,
ccx.display_name,
Expand All @@ -85,7 +87,7 @@ class Migration(migrations.Migration):
('ccx', '0001_initial'),
('ccx', '0002_customcourseforedx_structure_json'),
('course_overviews','0010_auto_20160329_2317'), # because we use course overview and are in the same release as that table is modified
('verified_track_content','0001_initial'), # because we use enrollement code and are in the same release as an enrollement related table is created
('verified_track_content','0001_initial'), # because we use enrollement code and are in the same release as an enrollement related table is created
]

operations = [
Expand Down
14 changes: 9 additions & 5 deletions lms/djangoapps/ccx/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ def get_valid_student_with_email(identifier):
return email, user


def ccx_students_enrolling_center(action, identifiers, email_students, course_key, email_params, coach):
def ccx_students_enrolling_center(request, action, identifiers, email_students, course_key, email_params, coach):
"""
Function to enroll or unenroll/revoke students.

Expand Down Expand Up @@ -268,7 +268,7 @@ def ccx_students_enrolling_center(action, identifiers, email_students, course_ke
log.info("%s", error)
errors.append(error)
break
enroll_email(course_key, email, auto_enroll=True, email_students=email_students, email_params=email_params)
enroll_email(request, course_key, email, auto_enroll=True, email_students=email_students, email_params=email_params)
elif action == 'Unenroll' or action == 'revoke':
for identifier in identifiers:
try:
Expand All @@ -277,7 +277,7 @@ def ccx_students_enrolling_center(action, identifiers, email_students, course_ke
log.info("%s", exp)
errors.append("{0}".format(exp))
continue
unenroll_email(course_key, email, email_students=email_students, email_params=email_params)
unenroll_email(request, course_key, email, email_students=email_students, email_params=email_params)
return errors


Expand Down Expand Up @@ -320,7 +320,7 @@ def is_email(identifier):
return True


def add_master_course_staff_to_ccx(master_course, ccx_key, display_name, send_email=True):
def add_master_course_staff_to_ccx(request, master_course, ccx_key, display_name, send_email=True):
"""
Add staff and instructor roles on ccx to all the staff and instructors members of master course.

Expand All @@ -344,6 +344,7 @@ def add_master_course_staff_to_ccx(master_course, ccx_key, display_name, send_em
try:
# Enroll the staff in the ccx
enroll_email(
request=request,
course_id=ccx_key,
student_email=staff.email,
auto_enroll=True,
Expand All @@ -369,6 +370,7 @@ def add_master_course_staff_to_ccx(master_course, ccx_key, display_name, send_em
try:
# Enroll the instructor in the ccx
enroll_email(
request=request,
course_id=ccx_key,
student_email=instructor.email,
auto_enroll=True,
Expand All @@ -389,7 +391,7 @@ def add_master_course_staff_to_ccx(master_course, ccx_key, display_name, send_em
continue


def remove_master_course_staff_from_ccx(master_course, ccx_key, display_name, send_email=True):
def remove_master_course_staff_from_ccx(request, master_course, ccx_key, display_name, send_email=True):
"""
Remove staff and instructor roles on ccx to all the staff and instructors members of master course.

Expand All @@ -414,6 +416,7 @@ def remove_master_course_staff_from_ccx(master_course, ccx_key, display_name, se

# Unenroll the staff on ccx.
unenroll_email(
request=request,
course_id=ccx_key,
student_email=staff.email,
email_students=send_email,
Expand All @@ -427,6 +430,7 @@ def remove_master_course_staff_from_ccx(master_course, ccx_key, display_name, se

# Unenroll the instructor on ccx.
unenroll_email(
request=request,
course_id=ccx_key,
student_email=instructor.email,
email_students=send_email,
Expand Down
Loading