feat: reminder emails (#30159)
send reminder emails to those users who saved course or program but not enroll within 15 days. VAN-887
This commit is contained in:
@@ -10,7 +10,7 @@ class SavedCourseAdmin(admin.ModelAdmin):
|
||||
Admin for the Saved Course table.
|
||||
"""
|
||||
|
||||
list_display = ['email', 'course_id']
|
||||
list_display = ['email', 'course_id', 'email_sent_count', 'reminder_email_sent']
|
||||
|
||||
search_fields = ['email', 'course_id']
|
||||
|
||||
@@ -20,7 +20,7 @@ class SavedProgramAdmin(admin.ModelAdmin):
|
||||
Admin for the Saved Program table.
|
||||
"""
|
||||
|
||||
list_display = ['email', 'program_uuid']
|
||||
list_display = ['email', 'program_uuid', 'email_sent_count', 'reminder_email_sent']
|
||||
|
||||
search_fields = ['email', 'program_uuid']
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ log = logging.getLogger(__name__)
|
||||
|
||||
POST_EMAIL_KEY = 'openedx.core.djangoapps.util.ratelimit.request_data_email'
|
||||
REAL_IP_KEY = 'openedx.core.djangoapps.util.ratelimit.real_ip'
|
||||
USER_SEND_SAVE_FOR_LATER_EMAIL = 'user.send.save.for.later.email'
|
||||
|
||||
|
||||
class CourseSaveForLaterApiView(APIView):
|
||||
@@ -61,6 +62,14 @@ class CourseSaveForLaterApiView(APIView):
|
||||
data = request.data
|
||||
course_id = data.get('course_id')
|
||||
email = data.get('email')
|
||||
org_img_url = data.get('org_img_url')
|
||||
marketing_url = data.get('marketing_url')
|
||||
weeks_to_complete = data.get('weeks_to_complete', 0)
|
||||
min_effort = data.get('min_effort', 0)
|
||||
max_effort = data.get('max_effort', 0)
|
||||
user_id = request.user.id
|
||||
pref_lang = request.COOKIES.get(settings.LANGUAGE_COOKIE_NAME, 'en')
|
||||
send_to_self = bool(not request.user.is_anonymous and request.user.email == email)
|
||||
|
||||
if getattr(request, 'limited', False):
|
||||
return Response({'error_code': 'rate-limited'}, status=403)
|
||||
@@ -80,12 +89,28 @@ class CourseSaveForLaterApiView(APIView):
|
||||
user_id=user.id,
|
||||
email=email,
|
||||
course_id=course_id,
|
||||
org_img_url=org_img_url,
|
||||
marketing_url=marketing_url,
|
||||
weeks_to_complete=weeks_to_complete,
|
||||
min_effort=min_effort,
|
||||
max_effort=max_effort,
|
||||
reminder_email_sent=False,
|
||||
)
|
||||
course_data = {
|
||||
'course': course,
|
||||
'send_to_self': send_to_self,
|
||||
'user_id': user_id,
|
||||
'pref-lang': pref_lang,
|
||||
'org_img_url': org_img_url,
|
||||
'marketing_url': marketing_url,
|
||||
'weeks_to_complete': weeks_to_complete,
|
||||
'min_effort': min_effort,
|
||||
'max_effort': max_effort,
|
||||
'type': 'course',
|
||||
'reminder': False,
|
||||
'braze_event': USER_SEND_SAVE_FOR_LATER_EMAIL,
|
||||
}
|
||||
if send_email(request, email, course_data):
|
||||
if send_email(email, course_data):
|
||||
return Response({'result': 'success'}, status=200)
|
||||
else:
|
||||
return Response({'error_code': 'email-not-send'}, status=400)
|
||||
@@ -120,6 +145,9 @@ class ProgramSaveForLaterApiView(APIView):
|
||||
data = request.data
|
||||
program_uuid = data.get('program_uuid')
|
||||
email = data.get('email')
|
||||
user_id = request.user.id
|
||||
pref_lang = request.COOKIES.get(settings.LANGUAGE_COOKIE_NAME, 'en')
|
||||
send_to_self = bool(not request.user.is_anonymous and request.user.email == email)
|
||||
|
||||
if getattr(request, 'limited', False):
|
||||
return Response({'error_code': 'rate-limited'}, status=403)
|
||||
@@ -139,9 +167,14 @@ class ProgramSaveForLaterApiView(APIView):
|
||||
if program:
|
||||
program_data = {
|
||||
'program': program,
|
||||
'send_to_self': send_to_self,
|
||||
'user_id': user_id,
|
||||
'pref-lang': pref_lang,
|
||||
'type': 'program',
|
||||
'reminder': False,
|
||||
'braze_event': USER_SEND_SAVE_FOR_LATER_EMAIL,
|
||||
}
|
||||
if send_email(request, email, program_data):
|
||||
if send_email(email, program_data):
|
||||
return Response({'result': 'success'}, status=200)
|
||||
else:
|
||||
return Response({'error_code': 'email-not-send'}, status=400)
|
||||
|
||||
@@ -12,7 +12,8 @@ from openedx.core.djangoapps.site_configuration import helpers as configuration_
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
USER_SENT_EMAIL_SAVE_FOR_LATER = 'edx.bi.user.saveforlater.email.sent'
|
||||
USER_SAVE_FOR_LATER_EMAIL_SENT = 'edx.bi.user.saveforlater.email.sent'
|
||||
USER_SAVE_FOR_LATER_REMINDER_EMAIL_SENT = 'edx.bi.user.saveforlater.reminder.email.sent'
|
||||
|
||||
|
||||
def _get_program_pacing(course_runs):
|
||||
@@ -25,30 +26,28 @@ def _get_program_pacing(course_runs):
|
||||
return 'Self-paced' if pacing == 'self_paced' else 'Instructor-led'
|
||||
|
||||
|
||||
def _get_event_properties(request, data):
|
||||
def _get_event_properties(data):
|
||||
"""
|
||||
set event properties for course and program which are required in braze email template
|
||||
"""
|
||||
lms_url = configuration_helpers.get_value('LMS_ROOT_URL', settings.LMS_ROOT_URL)
|
||||
event_properties = {
|
||||
'time': datetime.now().isoformat(),
|
||||
'name': 'user.send.save.for.later.email',
|
||||
'name': data.get('braze_event'),
|
||||
}
|
||||
|
||||
if data.get('type') == 'course':
|
||||
course = data.get('course')
|
||||
data = request.data
|
||||
org_img_url = data.get('org_img_url')
|
||||
marketing_url = data.get('marketing_url')
|
||||
event_properties.update({
|
||||
'properties': {
|
||||
'course_image_url': '{base_url}{image_path}'.format(
|
||||
base_url=lms_url, image_path=course.course_image_url
|
||||
),
|
||||
'partner_image_url': org_img_url,
|
||||
'partner_image_url': data.get('org_img_url'),
|
||||
'enroll_course_url': '{base_url}/register?course_id={course_id}&enrollment_action=enroll&email_opt_in='
|
||||
'false&save_for_later=true'.format(base_url=lms_url, course_id=course.id),
|
||||
'view_course_url': marketing_url + '?save_for_later=true' if marketing_url else '#',
|
||||
'view_course_url': data.get('marketing_url') + '?save_for_later=true' if data.get(
|
||||
'marketing_url') else '#',
|
||||
'display_name': course.display_name,
|
||||
'short_description': course.short_description,
|
||||
'weeks_to_complete': data.get('weeks_to_complete'),
|
||||
@@ -84,11 +83,11 @@ def _get_event_properties(request, data):
|
||||
return event_properties
|
||||
|
||||
|
||||
def send_email(request, email, data):
|
||||
def send_email(email, data):
|
||||
"""
|
||||
Send email through Braze
|
||||
"""
|
||||
event_properties = _get_event_properties(request, data)
|
||||
event_properties = _get_event_properties(data)
|
||||
braze_client = BrazeClient(
|
||||
api_key=settings.EDX_BRAZE_API_KEY,
|
||||
api_url=settings.EDX_BRAZE_API_SERVER,
|
||||
@@ -109,16 +108,16 @@ def send_email(request, email, data):
|
||||
event_properties.update({'user_alias': user_alias})
|
||||
attributes = [{
|
||||
'user_alias': user_alias,
|
||||
'pref-lang': request.COOKIES.get(settings.LANGUAGE_COOKIE_NAME, 'en')
|
||||
'pref-lang': data.get('pref-lang', 'en')
|
||||
}]
|
||||
|
||||
braze_client.track_user(events=[event_properties], attributes=attributes)
|
||||
|
||||
event_data = {
|
||||
'user_id': request.user.id,
|
||||
'user_id': data.get('user_id'),
|
||||
'category': 'save-for-later',
|
||||
'type': event_properties.get('type'),
|
||||
'send_to_self': bool(not request.user.is_anonymous and request.user.email == email),
|
||||
'send_to_self': data.get('send_to_self'),
|
||||
}
|
||||
if data.get('type') == 'program':
|
||||
program = data.get('program')
|
||||
@@ -128,7 +127,7 @@ def send_email(request, email, data):
|
||||
event_data.update({'course_key': str(course.id)})
|
||||
|
||||
tracker.emit(
|
||||
USER_SENT_EMAIL_SAVE_FOR_LATER,
|
||||
USER_SAVE_FOR_LATER_REMINDER_EMAIL_SENT if data.get('reminder') else USER_SAVE_FOR_LATER_EMAIL_SENT,
|
||||
event_data
|
||||
)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
"""
|
||||
Management command to send reminder emails.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from textwrap import dedent
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import BaseCommand
|
||||
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user
|
||||
|
||||
from lms.djangoapps.save_for_later.helper import send_email
|
||||
from lms.djangoapps.save_for_later.models import SavedCourse
|
||||
from common.djangoapps.student.models import CourseEnrollment
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from common.djangoapps.util.query import use_read_replica_if_available
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
USER_SEND_SAVE_FOR_LATER_REMINDER_EMAIL = 'user.send.save.for.later.reminder.email'
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""
|
||||
Command to send reminder emails to those users who
|
||||
saved course by email but not register within 15 days.
|
||||
|
||||
|
||||
Examples:
|
||||
|
||||
./manage.py lms send_course_reminder_emails --batch-size=100
|
||||
"""
|
||||
help = dedent(__doc__)
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--batch-size',
|
||||
type=int,
|
||||
default=1000,
|
||||
help='Maximum number of users to send reminder email in one chunk')
|
||||
|
||||
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)
|
||||
@@ -0,0 +1,85 @@
|
||||
"""
|
||||
Management command to send program reminder emails.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from textwrap import dedent
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management import BaseCommand
|
||||
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user
|
||||
|
||||
from lms.djangoapps.save_for_later.helper import send_email
|
||||
from lms.djangoapps.save_for_later.models import SavedProgram
|
||||
from lms.djangoapps.program_enrollments.api import get_program_enrollment
|
||||
from openedx.core.djangoapps.catalog.utils import get_programs
|
||||
from common.djangoapps.util.query import use_read_replica_if_available
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
USER_SEND_SAVE_FOR_LATER_REMINDER_EMAIL = 'user.send.save.for.later.reminder.email'
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""
|
||||
Command to send reminder emails to those users who saved
|
||||
program by email but not enroll program within 15 days.
|
||||
|
||||
|
||||
Examples:
|
||||
|
||||
./manage.py lms send_program_reminder_emails --batch-size=100
|
||||
"""
|
||||
help = dedent(__doc__)
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
'--batch-size',
|
||||
type=int,
|
||||
default=1000,
|
||||
help='Maximum number of users to send reminder email in one chunk')
|
||||
|
||||
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,
|
||||
}
|
||||
if user and get_program_enrollment(program_uuid=saved_program.uuid, user=user):
|
||||
continue
|
||||
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)
|
||||
@@ -0,0 +1,39 @@
|
||||
""" Test the test_send_course_reminder_emails command line script."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import ddt
|
||||
from django.core.management import call_command
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
|
||||
|
||||
from openedx.core.djangolib.testing.utils import skip_unless_lms
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from lms.djangoapps.save_for_later.tests.factories import SavedCourseFactory
|
||||
from lms.djangoapps.save_for_later.models import SavedCourse
|
||||
from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@skip_unless_lms
|
||||
class SavedCourseReminderEmailsTest(SharedModuleStoreTestCase):
|
||||
"""
|
||||
Test the test_send_course_reminder_emails management command
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.course_id = 'course-v1:edX+DemoX+Demo_Course'
|
||||
self.user = UserFactory(email='email@test.com', username='jdoe')
|
||||
self.saved_course = SavedCourseFactory.create(course_id=self.course_id, user_id=self.user.id)
|
||||
self.saved_course_1 = SavedCourseFactory.create(course_id=self.course_id)
|
||||
CourseOverviewFactory.create(id=self.saved_course.course_id)
|
||||
CourseOverviewFactory.create(id=self.saved_course_1.course_id)
|
||||
|
||||
def test_send_reminder_emails(self):
|
||||
with patch('lms.djangoapps.save_for_later.helper.BrazeClient') as mock_task:
|
||||
call_command('send_course_reminder_emails', '--batch-size=1')
|
||||
mock_task.assert_called()
|
||||
|
||||
saved_course = SavedCourse.objects.filter(course_id=self.course_id).first()
|
||||
assert saved_course.reminder_email_sent is True
|
||||
assert saved_course.email_sent_count > 0
|
||||
@@ -0,0 +1,38 @@
|
||||
""" Test the test_send_program_reminder_emails command line script."""
|
||||
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import ddt
|
||||
from django.core.management import call_command
|
||||
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
|
||||
|
||||
from openedx.core.djangolib.testing.utils import skip_unless_lms
|
||||
from lms.djangoapps.save_for_later.tests.factories import SavedPogramFactory
|
||||
from openedx.core.djangoapps.catalog.tests.factories import ProgramFactory
|
||||
from lms.djangoapps.save_for_later.models import SavedProgram
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
@skip_unless_lms
|
||||
class SavedProgramReminderEmailsTest(SharedModuleStoreTestCase):
|
||||
"""
|
||||
Test the send_program_reminder_emails management command
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.uuid = '587f6abe-bfa4-4125-9fbe-4789bf3f97f1'
|
||||
self.program = ProgramFactory(uuid=self.uuid)
|
||||
self.saved_program = SavedPogramFactory.create(program_uuid=self.uuid)
|
||||
|
||||
@patch('lms.djangoapps.save_for_later.management.commands.send_program_reminder_emails.get_programs')
|
||||
def test_send_reminder_emails(self, mock_get_programs):
|
||||
mock_get_programs.return_value = self.program
|
||||
with patch('lms.djangoapps.save_for_later.helper.BrazeClient') as mock_task:
|
||||
call_command('send_program_reminder_emails', '--batch-size=1')
|
||||
mock_task.assert_called()
|
||||
|
||||
saved_program = SavedProgram.objects.filter(program_uuid=self.uuid).first()
|
||||
assert saved_program.reminder_email_sent is True
|
||||
assert saved_program.email_sent_count > 0
|
||||
@@ -0,0 +1,66 @@
|
||||
# Generated by Django 3.2.12 on 2022-03-22 16:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('save_for_later', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='savedcourse',
|
||||
name='marketing_url',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='savedcourse',
|
||||
name='max_effort',
|
||||
field=models.IntegerField(null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='savedcourse',
|
||||
name='min_effort',
|
||||
field=models.IntegerField(null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='savedcourse',
|
||||
name='org_img_url',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='savedcourse',
|
||||
name='weeks_to_complete',
|
||||
field=models.IntegerField(null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='savedcourse',
|
||||
name='email_sent_count',
|
||||
field=models.IntegerField(default=0, null=True),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='savedcourse',
|
||||
unique_together={('email', 'course_id')},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='savedprogram',
|
||||
name='email_sent_count',
|
||||
field=models.IntegerField(default=0, null=True),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='savedprogram',
|
||||
unique_together={('email', 'program_uuid')},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='savedcourse',
|
||||
name='reminder_email_sent',
|
||||
field=models.BooleanField(default=False, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='savedprogram',
|
||||
name='reminder_email_sent',
|
||||
field=models.BooleanField(default=False, null=True),
|
||||
),
|
||||
]
|
||||
@@ -21,6 +21,20 @@ class SavedCourse(DeletableByUserValue, TimeStampedModel):
|
||||
user_id = models.IntegerField(null=True, blank=True)
|
||||
email = models.EmailField(db_index=True)
|
||||
course_id = CourseKeyField(max_length=255, db_index=True)
|
||||
marketing_url = models.CharField(max_length=255, null=True, blank=True)
|
||||
org_img_url = models.CharField(max_length=255, null=True, blank=True)
|
||||
weeks_to_complete = models.IntegerField(null=True)
|
||||
min_effort = models.IntegerField(null=True)
|
||||
max_effort = models.IntegerField(null=True)
|
||||
email_sent_count = models.IntegerField(null=True, default=0)
|
||||
reminder_email_sent = models.BooleanField(default=False, null=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('email', 'course_id',)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.email_sent_count = self.email_sent_count + 1
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class SavedProgram(DeletableByUserValue, TimeStampedModel):
|
||||
@@ -34,3 +48,12 @@ class SavedProgram(DeletableByUserValue, TimeStampedModel):
|
||||
user_id = models.IntegerField(null=True, blank=True)
|
||||
email = models.EmailField(db_index=True)
|
||||
program_uuid = models.UUIDField()
|
||||
email_sent_count = models.IntegerField(null=True, default=0)
|
||||
reminder_email_sent = models.BooleanField(default=False, null=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('email', 'program_uuid',)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
self.email_sent_count = self.email_sent_count + 1
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
46
lms/djangoapps/save_for_later/tests/factories.py
Normal file
46
lms/djangoapps/save_for_later/tests/factories.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""
|
||||
Provides factories for save_for_later models.
|
||||
"""
|
||||
|
||||
|
||||
from datetime import datetime
|
||||
from pytz import UTC
|
||||
|
||||
import factory
|
||||
from factory.django import DjangoModelFactory
|
||||
|
||||
from lms.djangoapps.save_for_later.models import SavedCourse, SavedProgram
|
||||
|
||||
|
||||
class SavedCourseFactory(DjangoModelFactory):
|
||||
"""
|
||||
Factory for SavedCourses objects
|
||||
"""
|
||||
class Meta:
|
||||
model = SavedCourse
|
||||
django_get_or_create = ('course_id', 'user_id')
|
||||
|
||||
email = 'abc@test.com'
|
||||
course_id = factory.Sequence('{}'.format)
|
||||
user_id = factory.Sequence('{}'.format)
|
||||
reminder_email_sent = False
|
||||
email_sent_count = 0
|
||||
created = datetime(2020, 1, 1, tzinfo=UTC)
|
||||
modified = datetime(2020, 2, 1, tzinfo=UTC)
|
||||
|
||||
|
||||
class SavedPogramFactory(DjangoModelFactory):
|
||||
"""
|
||||
Factory for SavedProgram objects
|
||||
"""
|
||||
class Meta:
|
||||
model = SavedProgram
|
||||
django_get_or_create = ('program_uuid', )
|
||||
|
||||
email = 'abc@test.com'
|
||||
user_id = factory.Sequence('{}'.format)
|
||||
program_uuid = factory.Sequence('{}'.format)
|
||||
reminder_email_sent = False
|
||||
email_sent_count = 0
|
||||
created = datetime(2020, 1, 1, tzinfo=UTC)
|
||||
modified = datetime(2020, 2, 1, tzinfo=UTC)
|
||||
@@ -1054,6 +1054,9 @@ ENABLE_COPPA_COMPLIANCE = False
|
||||
# VAN-741 - save for later api put behind a flag to make it only available for edX
|
||||
ENABLE_SAVE_FOR_LATER = False
|
||||
|
||||
# VAN-887 - save for later reminder emails threshold days
|
||||
SAVE_FOR_LATER_REMINDER_EMAIL_THRESHOLD = 15
|
||||
|
||||
############################# SET PATH INFORMATION #############################
|
||||
PROJECT_ROOT = path(__file__).abspath().dirname().dirname() # /edx-platform/lms
|
||||
REPO_ROOT = PROJECT_ROOT.dirname()
|
||||
|
||||
Reference in New Issue
Block a user