diff --git a/common/djangoapps/student/tests/test_bulk_email_settings.py b/common/djangoapps/student/tests/test_bulk_email_settings.py
index b2bca30f0b..2ca0652b4a 100644
--- a/common/djangoapps/student/tests/test_bulk_email_settings.py
+++ b/common/djangoapps/student/tests/test_bulk_email_settings.py
@@ -8,7 +8,6 @@ import unittest
from django.conf import settings
from django.core.urlresolvers import reverse
-from mock import patch
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from student.tests.factories import UserFactory, CourseEnrollmentFactory
@@ -18,7 +17,7 @@ from xmodule.modulestore.tests.factories import CourseFactory
# This import is for an lms djangoapp.
# Its testcases are only run under lms.
-from bulk_email.models import CourseAuthorization # pylint: disable=import-error
+from bulk_email.models import CourseAuthorization, BulkEmailFlag # pylint: disable=import-error
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
@@ -51,34 +50,38 @@ class TestStudentDashboardEmailView(SharedModuleStoreTestCase):
name=self.course.display_name.replace(' ', '_'),
)
- @patch.dict(settings.FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': True, 'REQUIRE_COURSE_EMAIL_AUTH': False})
+ def tearDown(self):
+ super(TestStudentDashboardEmailView, self).tearDown()
+ BulkEmailFlag.objects.all().delete()
+
def test_email_flag_true(self):
+ BulkEmailFlag.objects.create(enabled=True, require_course_email_auth=False)
# Assert that the URL for the email view is in the response
response = self.client.get(self.url)
self.assertTrue(self.email_modal_link in response.content)
- @patch.dict(settings.FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': False})
def test_email_flag_false(self):
+ BulkEmailFlag.objects.create(enabled=False)
# Assert that the URL for the email view is not in the response
response = self.client.get(self.url)
- self.assertFalse(self.email_modal_link in response.content)
+ self.assertNotIn(self.email_modal_link, response.content)
- @patch.dict(settings.FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': True, 'REQUIRE_COURSE_EMAIL_AUTH': True})
def test_email_unauthorized(self):
+ BulkEmailFlag.objects.create(enabled=True, require_course_email_auth=True)
# Assert that instructor email is not enabled for this course
- self.assertFalse(CourseAuthorization.instructor_email_enabled(self.course.id))
+ self.assertFalse(BulkEmailFlag.feature_enabled(self.course.id))
# Assert that the URL for the email view is not in the response
# if this course isn't authorized
response = self.client.get(self.url)
- self.assertFalse(self.email_modal_link in response.content)
+ self.assertNotIn(self.email_modal_link, response.content)
- @patch.dict(settings.FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': True, 'REQUIRE_COURSE_EMAIL_AUTH': True})
def test_email_authorized(self):
+ BulkEmailFlag.objects.create(enabled=True, require_course_email_auth=True)
# Authorize the course to use email
cauth = CourseAuthorization(course_id=self.course.id, email_enabled=True)
cauth.save()
# Assert that instructor email is enabled for this course
- self.assertTrue(CourseAuthorization.instructor_email_enabled(self.course.id))
+ self.assertTrue(BulkEmailFlag.feature_enabled(self.course.id))
# Assert that the URL for the email view is not in the response
# if this course isn't authorized
response = self.client.get(self.url)
@@ -117,15 +120,15 @@ class TestStudentDashboardEmailViewXMLBacked(SharedModuleStoreTestCase):
name='2012_Fall',
)
- @patch.dict(settings.FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': True, 'REQUIRE_COURSE_EMAIL_AUTH': False})
def test_email_flag_true_xml_store(self):
+ BulkEmailFlag.objects.create(enabled=True, require_course_email_auth=False)
# The flag is enabled, and since REQUIRE_COURSE_EMAIL_AUTH is False, all courses should
# be authorized to use email. But the course is not Mongo-backed (should not work)
response = self.client.get(self.url)
self.assertFalse(self.email_modal_link in response.content)
- @patch.dict(settings.FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': False, 'REQUIRE_COURSE_EMAIL_AUTH': False})
def test_email_flag_false_xml_store(self):
+ BulkEmailFlag.objects.create(enabled=False, require_course_email_auth=False)
# Email disabled, shouldn't see link.
response = self.client.get(self.url)
self.assertFalse(self.email_modal_link in response.content)
diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py
index eb75876933..0b0ac5633e 100644
--- a/common/djangoapps/student/views.py
+++ b/common/djangoapps/student/views.py
@@ -56,6 +56,7 @@ from student.models import (
from student.forms import AccountCreationForm, PasswordResetFormNoActive, get_registration_extension_form
from lms.djangoapps.commerce.utils import EcommerceService # pylint: disable=import-error
from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification # pylint: disable=import-error
+from bulk_email.models import Optout, BulkEmailFlag # pylint: disable=import-error
from certificates.models import CertificateStatuses, certificate_status_for_student
from certificates.api import ( # pylint: disable=import-error
get_certificate_url,
@@ -83,7 +84,6 @@ from external_auth.login_and_register import (
register as external_auth_register
)
-from bulk_email.models import Optout, CourseAuthorization
from lang_pref import LANGUAGE_KEY
import track.views
@@ -649,8 +649,7 @@ def dashboard(request):
# only show email settings for Mongo course and when bulk email is turned on
show_email_settings_for = frozenset(
enrollment.course_id for enrollment in course_enrollments if (
- settings.FEATURES['ENABLE_INSTRUCTOR_EMAIL'] and
- CourseAuthorization.instructor_email_enabled(enrollment.course_id)
+ BulkEmailFlag.feature_enabled(enrollment.course_id)
)
)
diff --git a/common/test/acceptance/pages/lms/instructor_dashboard.py b/common/test/acceptance/pages/lms/instructor_dashboard.py
index 0de7cafdcb..80b0c6c5ad 100644
--- a/common/test/acceptance/pages/lms/instructor_dashboard.py
+++ b/common/test/acceptance/pages/lms/instructor_dashboard.py
@@ -75,6 +75,15 @@ class InstructorDashboardPage(CoursePage):
timed_exam_section.wait_for_page()
return timed_exam_section
+ def select_bulk_email(self):
+ """
+ Selects the email tab and returns the bulk email section
+ """
+ self.q(css='a[data-section=send_email]').first.click()
+ email_section = BulkEmailPage(self.browser)
+ email_section.wait_for_page()
+ return email_section
+
@staticmethod
def get_asset_path(file_name):
"""
@@ -98,6 +107,62 @@ class InstructorDashboardPage(CoursePage):
return os.sep.join(folders_list_in_path)
+class BulkEmailPage(PageObject):
+ """
+ Bulk email section of the instructor dashboard.
+ This feature is controlled by an admin panel feature flag, which is turned on via database fixture for testing.
+ """
+ url = None
+
+ def is_browser_on_page(self):
+ return self.q(css='a[data-section=send_email].active-section').present
+
+ def _bounded_selector(self, selector):
+ """
+ Return `selector`, but limited to the bulk-email context.
+ """
+ return '.send-email {}'.format(selector)
+
+ def _select_recipient(self, recipient):
+ """
+ Selects the specified recipient from the selector. Assumes that recipient is not None.
+ """
+ recipient_selector_css = "select[name='send_to']"
+ select_option_by_text(
+ self.q(css=self._bounded_selector(recipient_selector_css)), recipient
+ )
+
+ def send_message(self, recipient):
+ """
+ Send a test message to the specified recipient.
+ """
+ send_css = "input[name='send']"
+ test_subject = "Hello"
+ test_body = "This is a test email"
+
+ self._select_recipient(recipient)
+ self.q(css=self._bounded_selector("input[name='subject']")).fill(test_subject)
+ self.q(css=self._bounded_selector("iframe#mce_0_ifr"))[0].click()
+ self.q(css=self._bounded_selector("iframe#mce_0_ifr"))[0].send_keys(test_body)
+
+ with self.handle_alert(confirm=True):
+ self.q(css=self._bounded_selector(send_css)).click()
+
+ def verify_message_queued_successfully(self):
+ """
+ Verifies that the "you email was queued" message appears.
+
+ Note that this does NOT ensure the message gets sent successfully, that functionality
+ is covered by the bulk_email unit tests.
+ """
+ confirmation_selector = self._bounded_selector(".msg-confirm")
+ expected_text = u"Your email was successfully queued for sending."
+ EmptyPromise(
+ lambda: expected_text in self.q(css=confirmation_selector)[0].text,
+ "Message Queued Confirmation"
+ ).fulfill()
+
+
class MembershipPage(PageObject):
"""
Membership section of the Instructor dashboard.
diff --git a/common/test/acceptance/tests/lms/test_lms_instructor_dashboard.py b/common/test/acceptance/tests/lms/test_lms_instructor_dashboard.py
index df4e0a2455..a05eedca66 100644
--- a/common/test/acceptance/tests/lms/test_lms_instructor_dashboard.py
+++ b/common/test/acceptance/tests/lms/test_lms_instructor_dashboard.py
@@ -46,6 +46,25 @@ class BaseInstructorDashboardTest(EventsTestMixin, UniqueCourseTest):
return instructor_dashboard_page
+@ddt.ddt
+class BulkEmailTest(BaseInstructorDashboardTest):
+ """
+ End-to-end tests for bulk emailing from instructor dash.
+ """
+ def setUp(self):
+ super(BulkEmailTest, self).setUp()
+ self.course_fixture = CourseFixture(**self.course_info).install()
+ self.log_in_as_instructor()
+ instructor_dashboard_page = self.visit_instructor_dashboard()
+ self.send_email_page = instructor_dashboard_page.select_bulk_email()
+
+ @ddt.data("Myself", "Staff and admins", "All (students, staff, and admins)")
+ def test_email_queued_for_sending(self, recipient):
+ self.assertTrue(self.send_email_page.is_browser_on_page())
+ self.send_email_page.send_message(recipient)
+ self.send_email_page.verify_message_queued_successfully()
+
+
@attr('shard_7')
class AutoEnrollmentWithCSVTest(BaseInstructorDashboardTest):
"""
diff --git a/common/test/db_cache/bok_choy_data_default.json b/common/test/db_cache/bok_choy_data_default.json
index 7db15d4d48..b3ca97e791 100644
--- a/common/test/db_cache/bok_choy_data_default.json
+++ b/common/test/db_cache/bok_choy_data_default.json
@@ -1 +1 @@
-[{"fields": {"model": "apiaccessrequest", "app_label": "api_admin"}, "model": "contenttypes.contenttype", "pk": 1}, {"fields": {"model": "permission", "app_label": "auth"}, "model": "contenttypes.contenttype", "pk": 2}, {"fields": {"model": "group", "app_label": "auth"}, "model": "contenttypes.contenttype", "pk": 3}, {"fields": {"model": "user", "app_label": "auth"}, "model": "contenttypes.contenttype", "pk": 4}, {"fields": {"model": "contenttype", "app_label": "contenttypes"}, "model": "contenttypes.contenttype", "pk": 5}, {"fields": {"model": "session", "app_label": "sessions"}, "model": "contenttypes.contenttype", "pk": 6}, {"fields": {"model": "site", "app_label": "sites"}, "model": "contenttypes.contenttype", "pk": 7}, {"fields": {"model": "taskmeta", "app_label": "djcelery"}, "model": "contenttypes.contenttype", "pk": 8}, {"fields": {"model": "tasksetmeta", "app_label": "djcelery"}, "model": "contenttypes.contenttype", "pk": 9}, {"fields": {"model": "intervalschedule", "app_label": "djcelery"}, "model": "contenttypes.contenttype", "pk": 10}, {"fields": {"model": "crontabschedule", "app_label": "djcelery"}, "model": "contenttypes.contenttype", "pk": 11}, {"fields": {"model": "periodictasks", "app_label": "djcelery"}, "model": "contenttypes.contenttype", "pk": 12}, {"fields": {"model": "periodictask", "app_label": "djcelery"}, "model": "contenttypes.contenttype", "pk": 13}, {"fields": {"model": "workerstate", "app_label": "djcelery"}, "model": "contenttypes.contenttype", "pk": 14}, {"fields": {"model": "taskstate", "app_label": "djcelery"}, "model": "contenttypes.contenttype", "pk": 15}, {"fields": {"model": "globalstatusmessage", "app_label": "status"}, "model": "contenttypes.contenttype", "pk": 16}, {"fields": {"model": "coursemessage", "app_label": "status"}, "model": "contenttypes.contenttype", "pk": 17}, {"fields": {"model": "assetbaseurlconfig", "app_label": "static_replace"}, "model": "contenttypes.contenttype", "pk": 18}, {"fields": {"model": "assetexcludedextensionsconfig", "app_label": "static_replace"}, "model": "contenttypes.contenttype", "pk": 19}, {"fields": {"model": "courseassetcachettlconfig", "app_label": "contentserver"}, "model": "contenttypes.contenttype", "pk": 20}, {"fields": {"model": "sitetheme", "app_label": "theming"}, "model": "contenttypes.contenttype", "pk": 21}, {"fields": {"model": "siteconfiguration", "app_label": "site_configuration"}, "model": "contenttypes.contenttype", "pk": 22}, {"fields": {"model": "siteconfigurationhistory", "app_label": "site_configuration"}, "model": "contenttypes.contenttype", "pk": 23}, {"fields": {"model": "studentmodule", "app_label": "courseware"}, "model": "contenttypes.contenttype", "pk": 24}, {"fields": {"model": "studentmodulehistory", "app_label": "courseware"}, "model": "contenttypes.contenttype", "pk": 25}, {"fields": {"model": "xmoduleuserstatesummaryfield", "app_label": "courseware"}, "model": "contenttypes.contenttype", "pk": 26}, {"fields": {"model": "xmodulestudentprefsfield", "app_label": "courseware"}, "model": "contenttypes.contenttype", "pk": 27}, {"fields": {"model": "xmodulestudentinfofield", "app_label": "courseware"}, "model": "contenttypes.contenttype", "pk": 28}, {"fields": {"model": "offlinecomputedgrade", "app_label": "courseware"}, "model": "contenttypes.contenttype", "pk": 29}, {"fields": {"model": "offlinecomputedgradelog", "app_label": "courseware"}, "model": "contenttypes.contenttype", "pk": 30}, {"fields": {"model": "studentfieldoverride", "app_label": "courseware"}, "model": "contenttypes.contenttype", "pk": 31}, {"fields": {"model": "anonymoususerid", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 32}, {"fields": {"model": "userstanding", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 33}, {"fields": {"model": "userprofile", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 34}, {"fields": {"model": "usersignupsource", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 35}, {"fields": {"model": "usertestgroup", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 36}, {"fields": {"model": "registration", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 37}, {"fields": {"model": "pendingnamechange", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 38}, {"fields": {"model": "pendingemailchange", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 39}, {"fields": {"model": "passwordhistory", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 40}, {"fields": {"model": "loginfailures", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 41}, {"fields": {"model": "historicalcourseenrollment", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 42}, {"fields": {"model": "courseenrollment", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 43}, {"fields": {"model": "manualenrollmentaudit", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 44}, {"fields": {"model": "courseenrollmentallowed", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 45}, {"fields": {"model": "courseaccessrole", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 46}, {"fields": {"model": "dashboardconfiguration", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 47}, {"fields": {"model": "linkedinaddtoprofileconfiguration", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 48}, {"fields": {"model": "entranceexamconfiguration", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 49}, {"fields": {"model": "languageproficiency", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 50}, {"fields": {"model": "courseenrollmentattribute", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 51}, {"fields": {"model": "enrollmentrefundconfiguration", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 52}, {"fields": {"model": "trackinglog", "app_label": "track"}, "model": "contenttypes.contenttype", "pk": 53}, {"fields": {"model": "ratelimitconfiguration", "app_label": "util"}, "model": "contenttypes.contenttype", "pk": 54}, {"fields": {"model": "certificatewhitelist", "app_label": "certificates"}, "model": "contenttypes.contenttype", "pk": 55}, {"fields": {"model": "generatedcertificate", "app_label": "certificates"}, "model": "contenttypes.contenttype", "pk": 56}, {"fields": {"model": "certificategenerationhistory", "app_label": "certificates"}, "model": "contenttypes.contenttype", "pk": 57}, {"fields": {"model": "certificateinvalidation", "app_label": "certificates"}, "model": "contenttypes.contenttype", "pk": 58}, {"fields": {"model": "examplecertificateset", "app_label": "certificates"}, "model": "contenttypes.contenttype", "pk": 59}, {"fields": {"model": "examplecertificate", "app_label": "certificates"}, "model": "contenttypes.contenttype", "pk": 60}, {"fields": {"model": "certificategenerationcoursesetting", "app_label": "certificates"}, "model": "contenttypes.contenttype", "pk": 61}, {"fields": {"model": "certificategenerationconfiguration", "app_label": "certificates"}, "model": "contenttypes.contenttype", "pk": 62}, {"fields": {"model": "certificatehtmlviewconfiguration", "app_label": "certificates"}, "model": "contenttypes.contenttype", "pk": 63}, {"fields": {"model": "certificatetemplate", "app_label": "certificates"}, "model": "contenttypes.contenttype", "pk": 64}, {"fields": {"model": "certificatetemplateasset", "app_label": "certificates"}, "model": "contenttypes.contenttype", "pk": 65}, {"fields": {"model": "instructortask", "app_label": "instructor_task"}, "model": "contenttypes.contenttype", "pk": 66}, {"fields": {"model": "courseusergroup", "app_label": "course_groups"}, "model": "contenttypes.contenttype", "pk": 67}, {"fields": {"model": "cohortmembership", "app_label": "course_groups"}, "model": "contenttypes.contenttype", "pk": 68}, {"fields": {"model": "courseusergrouppartitiongroup", "app_label": "course_groups"}, "model": "contenttypes.contenttype", "pk": 69}, {"fields": {"model": "coursecohortssettings", "app_label": "course_groups"}, "model": "contenttypes.contenttype", "pk": 70}, {"fields": {"model": "coursecohort", "app_label": "course_groups"}, "model": "contenttypes.contenttype", "pk": 71}, {"fields": {"model": "courseemail", "app_label": "bulk_email"}, "model": "contenttypes.contenttype", "pk": 72}, {"fields": {"model": "optout", "app_label": "bulk_email"}, "model": "contenttypes.contenttype", "pk": 73}, {"fields": {"model": "courseemailtemplate", "app_label": "bulk_email"}, "model": "contenttypes.contenttype", "pk": 74}, {"fields": {"model": "courseauthorization", "app_label": "bulk_email"}, "model": "contenttypes.contenttype", "pk": 75}, {"fields": {"model": "brandinginfoconfig", "app_label": "branding"}, "model": "contenttypes.contenttype", "pk": 76}, {"fields": {"model": "brandingapiconfig", "app_label": "branding"}, "model": "contenttypes.contenttype", "pk": 77}, {"fields": {"model": "externalauthmap", "app_label": "external_auth"}, "model": "contenttypes.contenttype", "pk": 78}, {"fields": {"model": "nonce", "app_label": "django_openid_auth"}, "model": "contenttypes.contenttype", "pk": 79}, {"fields": {"model": "association", "app_label": "django_openid_auth"}, "model": "contenttypes.contenttype", "pk": 80}, {"fields": {"model": "useropenid", "app_label": "django_openid_auth"}, "model": "contenttypes.contenttype", "pk": 81}, {"fields": {"model": "client", "app_label": "oauth2"}, "model": "contenttypes.contenttype", "pk": 82}, {"fields": {"model": "grant", "app_label": "oauth2"}, "model": "contenttypes.contenttype", "pk": 83}, {"fields": {"model": "accesstoken", "app_label": "oauth2"}, "model": "contenttypes.contenttype", "pk": 84}, {"fields": {"model": "refreshtoken", "app_label": "oauth2"}, "model": "contenttypes.contenttype", "pk": 85}, {"fields": {"model": "trustedclient", "app_label": "edx_oauth2_provider"}, "model": "contenttypes.contenttype", "pk": 86}, {"fields": {"model": "application", "app_label": "oauth2_provider"}, "model": "contenttypes.contenttype", "pk": 87}, {"fields": {"model": "grant", "app_label": "oauth2_provider"}, "model": "contenttypes.contenttype", "pk": 88}, {"fields": {"model": "accesstoken", "app_label": "oauth2_provider"}, "model": "contenttypes.contenttype", "pk": 89}, {"fields": {"model": "refreshtoken", "app_label": "oauth2_provider"}, "model": "contenttypes.contenttype", "pk": 90}, {"fields": {"model": "oauth2providerconfig", "app_label": "third_party_auth"}, "model": "contenttypes.contenttype", "pk": 91}, {"fields": {"model": "samlproviderconfig", "app_label": "third_party_auth"}, "model": "contenttypes.contenttype", "pk": 92}, {"fields": {"model": "samlconfiguration", "app_label": "third_party_auth"}, "model": "contenttypes.contenttype", "pk": 93}, {"fields": {"model": "samlproviderdata", "app_label": "third_party_auth"}, "model": "contenttypes.contenttype", "pk": 94}, {"fields": {"model": "ltiproviderconfig", "app_label": "third_party_auth"}, "model": "contenttypes.contenttype", "pk": 95}, {"fields": {"model": "providerapipermissions", "app_label": "third_party_auth"}, "model": "contenttypes.contenttype", "pk": 96}, {"fields": {"model": "nonce", "app_label": "oauth_provider"}, "model": "contenttypes.contenttype", "pk": 97}, {"fields": {"model": "scope", "app_label": "oauth_provider"}, "model": "contenttypes.contenttype", "pk": 98}, {"fields": {"model": "consumer", "app_label": "oauth_provider"}, "model": "contenttypes.contenttype", "pk": 99}, {"fields": {"model": "token", "app_label": "oauth_provider"}, "model": "contenttypes.contenttype", "pk": 100}, {"fields": {"model": "resource", "app_label": "oauth_provider"}, "model": "contenttypes.contenttype", "pk": 101}, {"fields": {"model": "article", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 102}, {"fields": {"model": "articleforobject", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 103}, {"fields": {"model": "articlerevision", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 104}, {"fields": {"model": "urlpath", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 105}, {"fields": {"model": "articleplugin", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 106}, {"fields": {"model": "reusableplugin", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 107}, {"fields": {"model": "simpleplugin", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 108}, {"fields": {"model": "revisionplugin", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 109}, {"fields": {"model": "revisionpluginrevision", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 110}, {"fields": {"model": "image", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 111}, {"fields": {"model": "imagerevision", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 112}, {"fields": {"model": "attachment", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 113}, {"fields": {"model": "attachmentrevision", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 114}, {"fields": {"model": "notificationtype", "app_label": "django_notify"}, "model": "contenttypes.contenttype", "pk": 115}, {"fields": {"model": "settings", "app_label": "django_notify"}, "model": "contenttypes.contenttype", "pk": 116}, {"fields": {"model": "subscription", "app_label": "django_notify"}, "model": "contenttypes.contenttype", "pk": 117}, {"fields": {"model": "notification", "app_label": "django_notify"}, "model": "contenttypes.contenttype", "pk": 118}, {"fields": {"model": "logentry", "app_label": "admin"}, "model": "contenttypes.contenttype", "pk": 119}, {"fields": {"model": "role", "app_label": "django_comment_common"}, "model": "contenttypes.contenttype", "pk": 120}, {"fields": {"model": "permission", "app_label": "django_comment_common"}, "model": "contenttypes.contenttype", "pk": 121}, {"fields": {"model": "note", "app_label": "notes"}, "model": "contenttypes.contenttype", "pk": 122}, {"fields": {"model": "splashconfig", "app_label": "splash"}, "model": "contenttypes.contenttype", "pk": 123}, {"fields": {"model": "userpreference", "app_label": "user_api"}, "model": "contenttypes.contenttype", "pk": 124}, {"fields": {"model": "usercoursetag", "app_label": "user_api"}, "model": "contenttypes.contenttype", "pk": 125}, {"fields": {"model": "userorgtag", "app_label": "user_api"}, "model": "contenttypes.contenttype", "pk": 126}, {"fields": {"model": "order", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 127}, {"fields": {"model": "orderitem", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 128}, {"fields": {"model": "invoice", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 129}, {"fields": {"model": "invoicetransaction", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 130}, {"fields": {"model": "invoiceitem", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 131}, {"fields": {"model": "courseregistrationcodeinvoiceitem", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 132}, {"fields": {"model": "invoicehistory", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 133}, {"fields": {"model": "courseregistrationcode", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 134}, {"fields": {"model": "registrationcoderedemption", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 135}, {"fields": {"model": "coupon", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 136}, {"fields": {"model": "couponredemption", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 137}, {"fields": {"model": "paidcourseregistration", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 138}, {"fields": {"model": "courseregcodeitem", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 139}, {"fields": {"model": "courseregcodeitemannotation", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 140}, {"fields": {"model": "paidcourseregistrationannotation", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 141}, {"fields": {"model": "certificateitem", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 142}, {"fields": {"model": "donationconfiguration", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 143}, {"fields": {"model": "donation", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 144}, {"fields": {"model": "coursemode", "app_label": "course_modes"}, "model": "contenttypes.contenttype", "pk": 145}, {"fields": {"model": "coursemodesarchive", "app_label": "course_modes"}, "model": "contenttypes.contenttype", "pk": 146}, {"fields": {"model": "coursemodeexpirationconfig", "app_label": "course_modes"}, "model": "contenttypes.contenttype", "pk": 147}, {"fields": {"model": "softwaresecurephotoverification", "app_label": "verify_student"}, "model": "contenttypes.contenttype", "pk": 148}, {"fields": {"model": "historicalverificationdeadline", "app_label": "verify_student"}, "model": "contenttypes.contenttype", "pk": 149}, {"fields": {"model": "verificationdeadline", "app_label": "verify_student"}, "model": "contenttypes.contenttype", "pk": 150}, {"fields": {"model": "verificationcheckpoint", "app_label": "verify_student"}, "model": "contenttypes.contenttype", "pk": 151}, {"fields": {"model": "verificationstatus", "app_label": "verify_student"}, "model": "contenttypes.contenttype", "pk": 152}, {"fields": {"model": "incoursereverificationconfiguration", "app_label": "verify_student"}, "model": "contenttypes.contenttype", "pk": 153}, {"fields": {"model": "icrvstatusemailsconfiguration", "app_label": "verify_student"}, "model": "contenttypes.contenttype", "pk": 154}, {"fields": {"model": "skippedreverification", "app_label": "verify_student"}, "model": "contenttypes.contenttype", "pk": 155}, {"fields": {"model": "darklangconfig", "app_label": "dark_lang"}, "model": "contenttypes.contenttype", "pk": 156}, {"fields": {"model": "microsite", "app_label": "microsite_configuration"}, "model": "contenttypes.contenttype", "pk": 157}, {"fields": {"model": "micrositehistory", "app_label": "microsite_configuration"}, "model": "contenttypes.contenttype", "pk": 158}, {"fields": {"model": "historicalmicrositeorganizationmapping", "app_label": "microsite_configuration"}, "model": "contenttypes.contenttype", "pk": 159}, {"fields": {"model": "micrositeorganizationmapping", "app_label": "microsite_configuration"}, "model": "contenttypes.contenttype", "pk": 160}, {"fields": {"model": "historicalmicrositetemplate", "app_label": "microsite_configuration"}, "model": "contenttypes.contenttype", "pk": 161}, {"fields": {"model": "micrositetemplate", "app_label": "microsite_configuration"}, "model": "contenttypes.contenttype", "pk": 162}, {"fields": {"model": "whitelistedrssurl", "app_label": "rss_proxy"}, "model": "contenttypes.contenttype", "pk": 163}, {"fields": {"model": "embargoedcourse", "app_label": "embargo"}, "model": "contenttypes.contenttype", "pk": 164}, {"fields": {"model": "embargoedstate", "app_label": "embargo"}, "model": "contenttypes.contenttype", "pk": 165}, {"fields": {"model": "restrictedcourse", "app_label": "embargo"}, "model": "contenttypes.contenttype", "pk": 166}, {"fields": {"model": "country", "app_label": "embargo"}, "model": "contenttypes.contenttype", "pk": 167}, {"fields": {"model": "countryaccessrule", "app_label": "embargo"}, "model": "contenttypes.contenttype", "pk": 168}, {"fields": {"model": "courseaccessrulehistory", "app_label": "embargo"}, "model": "contenttypes.contenttype", "pk": 169}, {"fields": {"model": "ipfilter", "app_label": "embargo"}, "model": "contenttypes.contenttype", "pk": 170}, {"fields": {"model": "coursererunstate", "app_label": "course_action_state"}, "model": "contenttypes.contenttype", "pk": 171}, {"fields": {"model": "mobileapiconfig", "app_label": "mobile_api"}, "model": "contenttypes.contenttype", "pk": 172}, {"fields": {"model": "usersocialauth", "app_label": "default"}, "model": "contenttypes.contenttype", "pk": 173}, {"fields": {"model": "nonce", "app_label": "default"}, "model": "contenttypes.contenttype", "pk": 174}, {"fields": {"model": "association", "app_label": "default"}, "model": "contenttypes.contenttype", "pk": 175}, {"fields": {"model": "code", "app_label": "default"}, "model": "contenttypes.contenttype", "pk": 176}, {"fields": {"model": "surveyform", "app_label": "survey"}, "model": "contenttypes.contenttype", "pk": 177}, {"fields": {"model": "surveyanswer", "app_label": "survey"}, "model": "contenttypes.contenttype", "pk": 178}, {"fields": {"model": "xblockasidesconfig", "app_label": "lms_xblock"}, "model": "contenttypes.contenttype", "pk": 179}, {"fields": {"model": "courseoverview", "app_label": "course_overviews"}, "model": "contenttypes.contenttype", "pk": 180}, {"fields": {"model": "courseoverviewtab", "app_label": "course_overviews"}, "model": "contenttypes.contenttype", "pk": 181}, {"fields": {"model": "courseoverviewimageset", "app_label": "course_overviews"}, "model": "contenttypes.contenttype", "pk": 182}, {"fields": {"model": "courseoverviewimageconfig", "app_label": "course_overviews"}, "model": "contenttypes.contenttype", "pk": 183}, {"fields": {"model": "coursestructure", "app_label": "course_structures"}, "model": "contenttypes.contenttype", "pk": 184}, {"fields": {"model": "corsmodel", "app_label": "corsheaders"}, "model": "contenttypes.contenttype", "pk": 185}, {"fields": {"model": "xdomainproxyconfiguration", "app_label": "cors_csrf"}, "model": "contenttypes.contenttype", "pk": 186}, {"fields": {"model": "commerceconfiguration", "app_label": "commerce"}, "model": "contenttypes.contenttype", "pk": 187}, {"fields": {"model": "creditprovider", "app_label": "credit"}, "model": "contenttypes.contenttype", "pk": 188}, {"fields": {"model": "creditcourse", "app_label": "credit"}, "model": "contenttypes.contenttype", "pk": 189}, {"fields": {"model": "creditrequirement", "app_label": "credit"}, "model": "contenttypes.contenttype", "pk": 190}, {"fields": {"model": "historicalcreditrequirementstatus", "app_label": "credit"}, "model": "contenttypes.contenttype", "pk": 191}, {"fields": {"model": "creditrequirementstatus", "app_label": "credit"}, "model": "contenttypes.contenttype", "pk": 192}, {"fields": {"model": "crediteligibility", "app_label": "credit"}, "model": "contenttypes.contenttype", "pk": 193}, {"fields": {"model": "historicalcreditrequest", "app_label": "credit"}, "model": "contenttypes.contenttype", "pk": 194}, {"fields": {"model": "creditrequest", "app_label": "credit"}, "model": "contenttypes.contenttype", "pk": 195}, {"fields": {"model": "creditconfig", "app_label": "credit"}, "model": "contenttypes.contenttype", "pk": 196}, {"fields": {"model": "courseteam", "app_label": "teams"}, "model": "contenttypes.contenttype", "pk": 197}, {"fields": {"model": "courseteammembership", "app_label": "teams"}, "model": "contenttypes.contenttype", "pk": 198}, {"fields": {"model": "xblockdisableconfig", "app_label": "xblock_django"}, "model": "contenttypes.contenttype", "pk": 199}, {"fields": {"model": "bookmark", "app_label": "bookmarks"}, "model": "contenttypes.contenttype", "pk": 200}, {"fields": {"model": "xblockcache", "app_label": "bookmarks"}, "model": "contenttypes.contenttype", "pk": 201}, {"fields": {"model": "programsapiconfig", "app_label": "programs"}, "model": "contenttypes.contenttype", "pk": 202}, {"fields": {"model": "selfpacedconfiguration", "app_label": "self_paced"}, "model": "contenttypes.contenttype", "pk": 203}, {"fields": {"model": "kvstore", "app_label": "thumbnail"}, "model": "contenttypes.contenttype", "pk": 204}, {"fields": {"model": "credentialsapiconfig", "app_label": "credentials"}, "model": "contenttypes.contenttype", "pk": 205}, {"fields": {"model": "milestone", "app_label": "milestones"}, "model": "contenttypes.contenttype", "pk": 206}, {"fields": {"model": "milestonerelationshiptype", "app_label": "milestones"}, "model": "contenttypes.contenttype", "pk": 207}, {"fields": {"model": "coursemilestone", "app_label": "milestones"}, "model": "contenttypes.contenttype", "pk": 208}, {"fields": {"model": "coursecontentmilestone", "app_label": "milestones"}, "model": "contenttypes.contenttype", "pk": 209}, {"fields": {"model": "usermilestone", "app_label": "milestones"}, "model": "contenttypes.contenttype", "pk": 210}, {"fields": {"model": "coursetalkwidgetconfiguration", "app_label": "coursetalk"}, "model": "contenttypes.contenttype", "pk": 211}, {"fields": {"model": "historicalapiaccessrequest", "app_label": "api_admin"}, "model": "contenttypes.contenttype", "pk": 212}, {"fields": {"model": "verifiedtrackcohortedcourse", "app_label": "verified_track_content"}, "model": "contenttypes.contenttype", "pk": 213}, {"fields": {"model": "badgeclass", "app_label": "badges"}, "model": "contenttypes.contenttype", "pk": 214}, {"fields": {"model": "badgeassertion", "app_label": "badges"}, "model": "contenttypes.contenttype", "pk": 215}, {"fields": {"model": "coursecompleteimageconfiguration", "app_label": "badges"}, "model": "contenttypes.contenttype", "pk": 216}, {"fields": {"model": "courseeventbadgesconfiguration", "app_label": "badges"}, "model": "contenttypes.contenttype", "pk": 217}, {"fields": {"model": "answer", "app_label": "mentoring"}, "model": "contenttypes.contenttype", "pk": 218}, {"fields": {"model": "answer", "app_label": "problem_builder"}, "model": "contenttypes.contenttype", "pk": 219}, {"fields": {"model": "share", "app_label": "problem_builder"}, "model": "contenttypes.contenttype", "pk": 220}, {"fields": {"model": "studentitem", "app_label": "submissions"}, "model": "contenttypes.contenttype", "pk": 221}, {"fields": {"model": "submission", "app_label": "submissions"}, "model": "contenttypes.contenttype", "pk": 222}, {"fields": {"model": "score", "app_label": "submissions"}, "model": "contenttypes.contenttype", "pk": 223}, {"fields": {"model": "scoresummary", "app_label": "submissions"}, "model": "contenttypes.contenttype", "pk": 224}, {"fields": {"model": "scoreannotation", "app_label": "submissions"}, "model": "contenttypes.contenttype", "pk": 225}, {"fields": {"model": "rubric", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 226}, {"fields": {"model": "criterion", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 227}, {"fields": {"model": "criterionoption", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 228}, {"fields": {"model": "assessment", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 229}, {"fields": {"model": "assessmentpart", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 230}, {"fields": {"model": "assessmentfeedbackoption", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 231}, {"fields": {"model": "assessmentfeedback", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 232}, {"fields": {"model": "peerworkflow", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 233}, {"fields": {"model": "peerworkflowitem", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 234}, {"fields": {"model": "trainingexample", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 235}, {"fields": {"model": "studenttrainingworkflow", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 236}, {"fields": {"model": "studenttrainingworkflowitem", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 237}, {"fields": {"model": "aiclassifierset", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 238}, {"fields": {"model": "aiclassifier", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 239}, {"fields": {"model": "aitrainingworkflow", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 240}, {"fields": {"model": "aigradingworkflow", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 241}, {"fields": {"model": "staffworkflow", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 242}, {"fields": {"model": "assessmentworkflow", "app_label": "workflow"}, "model": "contenttypes.contenttype", "pk": 243}, {"fields": {"model": "assessmentworkflowstep", "app_label": "workflow"}, "model": "contenttypes.contenttype", "pk": 244}, {"fields": {"model": "assessmentworkflowcancellation", "app_label": "workflow"}, "model": "contenttypes.contenttype", "pk": 245}, {"fields": {"model": "profile", "app_label": "edxval"}, "model": "contenttypes.contenttype", "pk": 246}, {"fields": {"model": "video", "app_label": "edxval"}, "model": "contenttypes.contenttype", "pk": 247}, {"fields": {"model": "coursevideo", "app_label": "edxval"}, "model": "contenttypes.contenttype", "pk": 248}, {"fields": {"model": "encodedvideo", "app_label": "edxval"}, "model": "contenttypes.contenttype", "pk": 249}, {"fields": {"model": "subtitle", "app_label": "edxval"}, "model": "contenttypes.contenttype", "pk": 250}, {"fields": {"model": "proctoredexam", "app_label": "edx_proctoring"}, "model": "contenttypes.contenttype", "pk": 251}, {"fields": {"model": "proctoredexamreviewpolicy", "app_label": "edx_proctoring"}, "model": "contenttypes.contenttype", "pk": 252}, {"fields": {"model": "proctoredexamreviewpolicyhistory", "app_label": "edx_proctoring"}, "model": "contenttypes.contenttype", "pk": 253}, {"fields": {"model": "proctoredexamstudentattempt", "app_label": "edx_proctoring"}, "model": "contenttypes.contenttype", "pk": 254}, {"fields": {"model": "proctoredexamstudentattempthistory", "app_label": "edx_proctoring"}, "model": "contenttypes.contenttype", "pk": 255}, {"fields": {"model": "proctoredexamstudentallowance", "app_label": "edx_proctoring"}, "model": "contenttypes.contenttype", "pk": 256}, {"fields": {"model": "proctoredexamstudentallowancehistory", "app_label": "edx_proctoring"}, "model": "contenttypes.contenttype", "pk": 257}, {"fields": {"model": "proctoredexamsoftwaresecurereview", "app_label": "edx_proctoring"}, "model": "contenttypes.contenttype", "pk": 258}, {"fields": {"model": "proctoredexamsoftwaresecurereviewhistory", "app_label": "edx_proctoring"}, "model": "contenttypes.contenttype", "pk": 259}, {"fields": {"model": "proctoredexamsoftwaresecurecomment", "app_label": "edx_proctoring"}, "model": "contenttypes.contenttype", "pk": 260}, {"fields": {"model": "organization", "app_label": "organizations"}, "model": "contenttypes.contenttype", "pk": 261}, {"fields": {"model": "organizationcourse", "app_label": "organizations"}, "model": "contenttypes.contenttype", "pk": 262}, {"fields": {"model": "studentmodulehistoryextended", "app_label": "coursewarehistoryextended"}, "model": "contenttypes.contenttype", "pk": 263}, {"fields": {"model": "videouploadconfig", "app_label": "contentstore"}, "model": "contenttypes.contenttype", "pk": 264}, {"fields": {"model": "pushnotificationconfig", "app_label": "contentstore"}, "model": "contenttypes.contenttype", "pk": 265}, {"fields": {"model": "coursecreator", "app_label": "course_creators"}, "model": "contenttypes.contenttype", "pk": 266}, {"fields": {"model": "studioconfig", "app_label": "xblock_config"}, "model": "contenttypes.contenttype", "pk": 267}, {"fields": {"domain": "example.com", "name": "example.com"}, "model": "sites.site", "pk": 1}, {"fields": {"plain_template": "{course_title}\n\n{{message_body}}\r\n----\r\nCopyright 2013 edX, All rights reserved.\r\n----\r\nConnect with edX:\r\nFacebook (http://facebook.com/edxonline)\r\nTwitter (http://twitter.com/edxonline)\r\nGoogle+ (https://plus.google.com/108235383044095082735)\r\nMeetup (http://www.meetup.com/edX-Communities/)\r\n----\r\nThis email was automatically sent from {platform_name}.\r\nYou are receiving this email at address {email} because you are enrolled in {course_title}\r\n(URL: {course_url} ).\r\nTo stop receiving email like this, update your course email settings at {email_settings_url}.\r\n", "html_template": "
Update from {course_title}