Legacy email tests, removed duplicate code, updated comments, fixed CSS
This commit is contained in:
committed by
Brian Wilson
parent
fd54b060d8
commit
8a30e9ba8f
@@ -49,19 +49,6 @@ def register_by_course_id(course_id, username='robot', password='test', is_staff
|
||||
u.save()
|
||||
CourseEnrollment.enroll(u, course_id)
|
||||
|
||||
@world.absorb
|
||||
def add_to_course_staff(username, course_num):
|
||||
"""
|
||||
Add the user with `username` to the course staff group
|
||||
for `course_num`.
|
||||
"""
|
||||
# Based on code in lms/djangoapps/courseware/access.py
|
||||
group_name = "instructor_{}".format(course_num)
|
||||
group, _ = Group.objects.get_or_create(name=group_name)
|
||||
group.save()
|
||||
|
||||
user = User.objects.get(username=username)
|
||||
user.groups.add(group)
|
||||
|
||||
@world.absorb
|
||||
def add_to_course_staff(username, course_num):
|
||||
|
||||
@@ -70,6 +70,23 @@ def css_has_text(css_selector, text, index=0, strip=False):
|
||||
return actual_text == text
|
||||
|
||||
|
||||
@world.absorb
|
||||
def css_has_value(css_selector, value, index=0):
|
||||
"""
|
||||
Return a boolean indicating whether the element with
|
||||
`css_selector` has the specified `value`.
|
||||
|
||||
If there are multiple elements matching the css selector,
|
||||
use `index` to indicate which one.
|
||||
"""
|
||||
# If we're expecting a non-empty string, give the page
|
||||
# a chance to fill in values
|
||||
if value:
|
||||
world.wait_for(lambda _: world.css_value(css_selector, index=index))
|
||||
|
||||
return world.css_value(css_selector, index=index) == value
|
||||
|
||||
|
||||
@world.absorb
|
||||
def wait_for(func, timeout=5):
|
||||
WebDriverWait(
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
@shard_2
|
||||
Feature: Bulk Email
|
||||
As an instructor,
|
||||
As an instructor or course staff,
|
||||
In order to communicate with students and staff
|
||||
I want to send email to staff and students in a course.
|
||||
|
||||
Scenario: Send bulk email
|
||||
Given I am an instructor for a course
|
||||
Given I am "<Role>" for a course
|
||||
When I send email to "<Recipient>"
|
||||
Then Email is sent to "<Recipient>"
|
||||
|
||||
Examples:
|
||||
| Recipient |
|
||||
| myself |
|
||||
| course staff |
|
||||
| students, staff, and instructors |
|
||||
| Role | Recipient |
|
||||
| instructor | myself |
|
||||
| instructor | course staff |
|
||||
| instructor | students, staff, and instructors |
|
||||
| staff | myself |
|
||||
| staff | course staff |
|
||||
| staff | students, staff, and instructors |
|
||||
|
||||
|
||||
@@ -2,25 +2,22 @@
|
||||
Define steps for bulk email acceptance test.
|
||||
"""
|
||||
|
||||
#pylint: disable=C0111
|
||||
#pylint: disable=W0621
|
||||
|
||||
from lettuce import world, step
|
||||
from lettuce.django import mail
|
||||
from nose.tools import assert_in, assert_true, assert_equal
|
||||
from nose.tools import assert_in, assert_true, assert_equal # pylint: disable=E0611
|
||||
from django.core.management import call_command
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
@step(u'I am an instructor for a course')
|
||||
def i_am_an_instructor(step):
|
||||
def i_am_an_instructor(step): # pylint: disable=W0613
|
||||
|
||||
# Clear existing courses to avoid conflicts
|
||||
world.clear_courses()
|
||||
|
||||
# Create a new course
|
||||
course = world.CourseFactory.create(
|
||||
org='edx',
|
||||
number='999',
|
||||
display_name='Test Course'
|
||||
)
|
||||
|
||||
# Register the instructor as staff for the course
|
||||
world.register_by_course_id(
|
||||
'edx/999/Test_Course',
|
||||
@@ -59,14 +56,14 @@ def i_am_an_instructor(step):
|
||||
# Dictionary mapping a description of the email recipient
|
||||
# to the corresponding <option> value in the UI.
|
||||
SEND_TO_OPTIONS = {
|
||||
'myself': 'myself',
|
||||
'course staff': 'staff',
|
||||
'students, staff, and instructors': 'all'
|
||||
'myself': 'myself',
|
||||
'course staff': 'staff',
|
||||
'students, staff, and instructors': 'all'
|
||||
}
|
||||
|
||||
|
||||
@step(u'I send email to "([^"]*)"')
|
||||
def when_i_send_an_email(step, recipient):
|
||||
def when_i_send_an_email(recipient):
|
||||
|
||||
# Check that the recipient is valid
|
||||
assert_in(
|
||||
@@ -99,10 +96,9 @@ def when_i_send_an_email(step, recipient):
|
||||
world.css_click('input[name="send"]')
|
||||
|
||||
# Expect to see a message that the email was sent
|
||||
# TODO -- identify the message by CSS ID instead of index
|
||||
expected_msg = "Your email was successfully queued for sending."
|
||||
assert_true(
|
||||
world.css_has_text('div.request-response', expected_msg, index=1, allow_blank=False),
|
||||
world.css_has_text('div.request-response', expected_msg, '#request-response', allow_blank=False),
|
||||
msg="Could not find email success message."
|
||||
)
|
||||
|
||||
@@ -117,8 +113,9 @@ EXPECTED_ADDRESSES = {
|
||||
|
||||
UNSUBSCRIBE_MSG = 'To stop receiving email like this'
|
||||
|
||||
|
||||
@step(u'Email is sent to "([^"]*)"')
|
||||
def then_the_email_is_sent(step, recipient):
|
||||
def then_the_email_is_sent(recipient):
|
||||
|
||||
# Check that the recipient is valid
|
||||
assert_in(
|
||||
@@ -129,8 +126,8 @@ def then_the_email_is_sent(step, recipient):
|
||||
# Retrieve messages. Because we are using celery in "always eager"
|
||||
# mode, we expect all messages to be sent by this point.
|
||||
messages = []
|
||||
while not mail.queue.empty():
|
||||
messages.append(mail.queue.get())
|
||||
while not mail.queue.empty(): # pylint: disable=E1101
|
||||
messages.append(mail.queue.get()) # pylint: disable=E1101
|
||||
|
||||
# Check that we got the right number of messages
|
||||
assert_equal(
|
||||
@@ -143,8 +140,8 @@ def then_the_email_is_sent(step, recipient):
|
||||
# Check that the message properties were correct
|
||||
recipients = []
|
||||
for msg in messages:
|
||||
assert_equal(msg.subject, u'[Test Course] Hello')
|
||||
assert_equal(msg.from_email, u'"Test Course" Course Staff <course-updates@edx.org>')
|
||||
assert_in('Hello', msg.subject)
|
||||
assert_in(settings.DEFAULT_BULK_FROM_EMAIL, msg.from_email)
|
||||
|
||||
# Message body should have the message we sent
|
||||
# and an unsubscribe message
|
||||
@@ -153,7 +150,7 @@ def then_the_email_is_sent(step, recipient):
|
||||
|
||||
# Should have alternative HTML form
|
||||
assert_equal(len(msg.alternatives), 1)
|
||||
content, mime_type = msg.alternatives[0]
|
||||
content = msg.alternatives[0]
|
||||
assert_in('test message', content)
|
||||
assert_in(UNSUBSCRIBE_MSG, content)
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import json
|
||||
import requests
|
||||
from urllib import quote
|
||||
from django.test import TestCase
|
||||
from nose.tools import raises
|
||||
from mock import Mock, patch
|
||||
from django.test.utils import override_settings
|
||||
from django.core.urlresolvers import reverse
|
||||
@@ -695,51 +696,44 @@ class TestInstructorAPIRegradeTask(ModuleStoreTestCase, LoginEnrollmentTestCase)
|
||||
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
|
||||
class TestInstructorSendEmail(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
"""
|
||||
fill this out
|
||||
Checks that only instructors have access to email endpoints, and that
|
||||
these endpoints are only accessible with courses that actually exist,
|
||||
only with valid email messages.
|
||||
"""
|
||||
def setUp(self):
|
||||
self.instructor = AdminFactory.create()
|
||||
self.course = CourseFactory.create()
|
||||
self.client.login(username=self.instructor.username, password='test')
|
||||
|
||||
test_subject = u'\u1234 test subject'
|
||||
test_message = u'\u6824 test message'
|
||||
self.full_test_message = {
|
||||
'send_to': 'staff',
|
||||
'subject': test_subject,
|
||||
'message': test_message,
|
||||
}
|
||||
|
||||
def test_send_email_as_logged_in_instructor(self):
|
||||
url = reverse('send_email', kwargs={'course_id': self.course.id})
|
||||
response = self.client.post(url,{
|
||||
'send_to': 'staff',
|
||||
'subject': 'test subject',
|
||||
'message': 'test message',
|
||||
})
|
||||
response = self.client.post(url, self.full_test_message)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_send_email_but_not_logged_in(self):
|
||||
self.client.logout()
|
||||
url = reverse('send_email', kwargs={'course_id': self.course.id})
|
||||
response = self.client.post(url, {
|
||||
'send_to': 'staff',
|
||||
'subject': 'test subject',
|
||||
'message': 'test message',
|
||||
})
|
||||
response = self.client.post(url, self.full_test_message)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_send_email_but_not_staff(self):
|
||||
self.client.logout()
|
||||
self.student = UserFactory()
|
||||
self.client.login(username=self.student.username, password='test')
|
||||
student = UserFactory()
|
||||
self.client.login(username=student.username, password='test')
|
||||
url = reverse('send_email', kwargs={'course_id': self.course.id})
|
||||
response = self.client.post(url, {
|
||||
'send_to': 'staff',
|
||||
'subject': 'test subject',
|
||||
'message': 'test message',
|
||||
})
|
||||
response = self.client.post(url, self.full_test_message)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_send_email_but_course_not_exist(self):
|
||||
url = reverse('send_email', kwargs={'course_id': 'GarbageCourse/DNE/NoTerm'})
|
||||
response = self.client.post(url, {
|
||||
'send_to': 'staff',
|
||||
'subject': 'test subject',
|
||||
'message': 'test message',
|
||||
})
|
||||
response = self.client.post(url, self.full_test_message)
|
||||
self.assertNotEqual(response.status_code, 200)
|
||||
|
||||
def test_send_email_no_sendto(self):
|
||||
@@ -747,7 +741,7 @@ class TestInstructorSendEmail(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
response = self.client.post(url, {
|
||||
'subject': 'test subject',
|
||||
'message': 'test message',
|
||||
})
|
||||
})
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_send_email_no_subject(self):
|
||||
@@ -755,7 +749,7 @@ class TestInstructorSendEmail(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
response = self.client.post(url, {
|
||||
'send_to': 'staff',
|
||||
'message': 'test message',
|
||||
})
|
||||
})
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_send_email_no_message(self):
|
||||
@@ -763,81 +757,7 @@ class TestInstructorSendEmail(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
response = self.client.post(url, {
|
||||
'send_to': 'staff',
|
||||
'subject': 'test subject',
|
||||
})
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
|
||||
class TestInstructorSendEmail(ModuleStoreTestCase, LoginEnrollmentTestCase):
|
||||
"""
|
||||
fill this out
|
||||
"""
|
||||
def setUp(self):
|
||||
self.instructor = AdminFactory.create()
|
||||
self.course = CourseFactory.create()
|
||||
self.client.login(username=self.instructor.username, password='test')
|
||||
|
||||
def test_send_email_as_logged_in_instructor(self):
|
||||
url = reverse('send_email', kwargs={'course_id': self.course.id})
|
||||
response = self.client.get(url,{
|
||||
'send_to': 'staff',
|
||||
'subject': 'test subject',
|
||||
'message': 'test message',
|
||||
})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_send_email_but_not_logged_in(self):
|
||||
self.client.logout()
|
||||
url = reverse('send_email', kwargs={'course_id': self.course.id})
|
||||
response = self.client.get(url, {
|
||||
'send_to': 'staff',
|
||||
'subject': 'test subject',
|
||||
'message': 'test message',
|
||||
})
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_send_email_but_not_staff(self):
|
||||
self.client.logout()
|
||||
self.student = UserFactory()
|
||||
self.client.login(username=self.student.username, password='test')
|
||||
url = reverse('send_email', kwargs={'course_id': self.course.id})
|
||||
response = self.client.get(url, {
|
||||
'send_to': 'staff',
|
||||
'subject': 'test subject',
|
||||
'message': 'test message',
|
||||
})
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_send_email_but_course_not_exist(self):
|
||||
url = reverse('send_email', kwargs={'course_id': 'GarbageCourse/DNE/NoTerm'})
|
||||
response = self.client.get(url, {
|
||||
'send_to': 'staff',
|
||||
'subject': 'test subject',
|
||||
'message': 'test message',
|
||||
})
|
||||
self.assertNotEqual(response.status_code, 200)
|
||||
|
||||
def test_send_email_no_sendto(self):
|
||||
url = reverse('send_email', kwargs={'course_id': self.course.id})
|
||||
response = self.client.get(url, {
|
||||
'subject': 'test subject',
|
||||
'message': 'test message',
|
||||
})
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_send_email_no_subject(self):
|
||||
url = reverse('send_email', kwargs={'course_id': self.course.id})
|
||||
response = self.client.get(url, {
|
||||
'send_to': 'staff',
|
||||
'message': 'test message',
|
||||
})
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
def test_send_email_no_message(self):
|
||||
url = reverse('send_email', kwargs={'course_id': self.course.id})
|
||||
response = self.client.get(url, {
|
||||
'send_to': 'staff',
|
||||
'subject': 'test subject',
|
||||
})
|
||||
})
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
||||
|
||||
@@ -1043,8 +963,7 @@ class TestInstructorAPIHelpers(TestCase):
|
||||
output = 'i4x://MITx/6.002x/problem/L2Node1'
|
||||
self.assertEqual(_msk_from_problem_urlname(*args), output)
|
||||
|
||||
# TODO add this back in as soon as i know where the heck "raises" comes from
|
||||
#@raises(ValueError)
|
||||
#def test_msk_from_problem_urlname_error(self):
|
||||
# args = ('notagoodcourse', 'L2Node1')
|
||||
# _msk_from_problem_urlname(*args)
|
||||
@raises(ValueError)
|
||||
def test_msk_from_problem_urlname_error(self):
|
||||
args = ('notagoodcourse', 'L2Node1')
|
||||
_msk_from_problem_urlname(*args)
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
"""
|
||||
Unit tests for email feature flag in instructor dashboard
|
||||
and student dashboard. Additionally tests that bulk email
|
||||
is always disabled for non-Mongo backed courses, regardless
|
||||
of email feature flag.
|
||||
Unit tests for email feature flag in new instructor dashboard.
|
||||
Additionally tests that bulk email is always disabled for
|
||||
non-Mongo backed courses, regardless of email feature flag.
|
||||
"""
|
||||
|
||||
from django.test.utils import override_settings
|
||||
@@ -10,28 +9,19 @@ from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE
|
||||
from student.tests.factories import AdminFactory, UserFactory, CourseEnrollmentFactory
|
||||
from student.tests.factories import AdminFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from xmodule.modulestore import XML_MODULESTORE_TYPE
|
||||
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
|
||||
|
||||
from mock import patch
|
||||
|
||||
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
|
||||
class TestNewInstructorDashboardEmailView(ModuleStoreTestCase):
|
||||
"""
|
||||
Check for email view displayed with flag
|
||||
"""
|
||||
# will need to check for Mongo vs XML, ENABLED vs not enabled,
|
||||
# is studio course vs not studio course
|
||||
# section_data
|
||||
# what is html_module?
|
||||
# which are API lines
|
||||
|
||||
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
|
||||
class TestInstructorDashboardEmailView(ModuleStoreTestCase):
|
||||
class TestNewInstructorDashboardEmailViewMongoBacked(ModuleStoreTestCase):
|
||||
"""
|
||||
Check for email view displayed with flag
|
||||
Check for email view on the new instructor dashboard
|
||||
for Mongo-backed courses
|
||||
"""
|
||||
def setUp(self):
|
||||
self.course = CourseFactory.create()
|
||||
@@ -51,64 +41,52 @@ class TestInstructorDashboardEmailView(ModuleStoreTestCase):
|
||||
"""
|
||||
patch.stopall()
|
||||
|
||||
# Enabled and IS mongo
|
||||
# In order for bulk email to work, we must have both the ENABLE_INSTRUCTOR_EMAIL_FLAG
|
||||
# set to True and for the course to be Mongo-backed.
|
||||
# The flag is enabled and the course is Mongo-backed (should work)
|
||||
@patch.dict(settings.MITX_FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': True})
|
||||
def test_email_flag_true(self):
|
||||
def test_email_flag_true_mongo_true(self):
|
||||
# Assert that the URL for the email view is in the response
|
||||
response = self.client.get(self.url)
|
||||
self.assertTrue(self.email_link in response.content)
|
||||
self.assertIn(self.email_link, response.content)
|
||||
|
||||
send_to_label = '<label for="id_to">Send to:</label>'
|
||||
self.assertTrue(send_to_label in response.content)
|
||||
self.assertEqual(response.status_code,200)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# Disabled but IS mongo
|
||||
# The course is Mongo-backed but the flag is disabled (should not work)
|
||||
@patch.dict(settings.MITX_FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': False})
|
||||
def test_email_flag_false(self):
|
||||
def test_email_flag_false_mongo_true(self):
|
||||
# Assert that the URL for the email view is not in the response
|
||||
response = self.client.get(self.url)
|
||||
self.assertFalse(self.email_link in response.content)
|
||||
|
||||
# Enabled but NOT mongo
|
||||
@patch.dict(settings.MITX_FEATURES,{'ENABLE_INSTRUCTOR_EMAIL': True})
|
||||
def test_email_flag_false(self):
|
||||
with patch('xmodule.modulestore.mongo.base.MongoModuleStore.get_modulestore_type') as mock_modulestore:
|
||||
mock_modulestore.return_value = XML_MODULESTORE_TYPE
|
||||
|
||||
response = self.client.get(self.url)
|
||||
self.assertFalse(self.email_link in response.content)
|
||||
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
|
||||
class TestNewInstructorDashboardEmailViewXMLBacked(ModuleStoreTestCase):
|
||||
"""
|
||||
Check for email view on the new instructor dashboard
|
||||
"""
|
||||
def setUp(self):
|
||||
self.course_name = 'edX/toy/2012_Fall'
|
||||
|
||||
# Create instructor account
|
||||
instructor = AdminFactory.create()
|
||||
self.client.login(username=instructor.username, password="test")
|
||||
|
||||
# URL for instructor dash
|
||||
self.url = reverse('instructor_dashboard_2', kwargs={'course_id': self.course_name})
|
||||
# URL for email view
|
||||
self.email_link = '<a href="" data-section="send_email">Email</a>'
|
||||
|
||||
# The flag is enabled but the course is not Mongo-backed (should not work)
|
||||
@patch.dict(settings.MITX_FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': True})
|
||||
def test_email_flag_true_xml_store(self):
|
||||
# If the enable email setting is enabled, but this is an XML backed course,
|
||||
# the email view shouldn't be available on the instructor dashboard.
|
||||
def test_email_flag_true_mongo_false(self):
|
||||
response = self.client.get(self.url)
|
||||
self.assertFalse(self.email_link in response.content)
|
||||
|
||||
# The course factory uses a MongoModuleStore backing, so patch the
|
||||
# `get_modulestore_type` method to pretend to be XML-backed.
|
||||
# This is OK; we're simply testing that the `is_mongo_modulestore_type` flag
|
||||
# in `instructor/views/legacy.py` is doing the correct thing.
|
||||
|
||||
with patch('xmodule.modulestore.mongo.base.MongoModuleStore.get_modulestore_type') as mock_modulestore:
|
||||
mock_modulestore.return_value = XML_MODULESTORE_TYPE
|
||||
|
||||
# Assert that the URL for the email view is not in the response
|
||||
response = self.client.get(self.url)
|
||||
self.assertFalse(self.email_link in response.content)
|
||||
|
||||
# Disabled and IS Mongo
|
||||
# The flag is disabled and the course is not Mongo-backed (should not work)
|
||||
@patch.dict(settings.MITX_FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': False})
|
||||
def test_email_flag_true_xml_store(self):
|
||||
# If the enable email setting is enabled, but this is an XML backed course,
|
||||
# the email view shouldn't be available on the instructor dashboard.
|
||||
|
||||
# The course factory uses a MongoModuleStore backing, so patch the
|
||||
# `get_modulestore_type` method to pretend to be XML-backed.
|
||||
# This is OK; we're simply testing that the `is_mongo_modulestore_type` flag
|
||||
# in `instructor/views/legacy.py` is doing the correct thing.
|
||||
|
||||
with patch('xmodule.modulestore.mongo.base.MongoModuleStore.get_modulestore_type') as mock_modulestore:
|
||||
mock_modulestore.return_value = XML_MODULESTORE_TYPE
|
||||
|
||||
# Assert that the URL for the email view is not in the response
|
||||
response = self.client.get(self.url)
|
||||
self.assertFalse(self.email_link in response.content)
|
||||
def test_email_flag_false_mongo_false(self):
|
||||
response = self.client.get(self.url)
|
||||
self.assertFalse(self.email_link in response.content)
|
||||
|
||||
142
lms/djangoapps/instructor/tests/test_legacy_email.py
Normal file
142
lms/djangoapps/instructor/tests/test_legacy_email.py
Normal file
@@ -0,0 +1,142 @@
|
||||
"""
|
||||
Unit tests for email feature flag in legacy instructor dashboard
|
||||
and student dashboard. Additionally tests that bulk email
|
||||
is always disabled for non-Mongo backed courses, regardless
|
||||
of email feature flag.
|
||||
"""
|
||||
|
||||
from django.test.utils import override_settings
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE
|
||||
from student.tests.factories import AdminFactory, UserFactory, CourseEnrollmentFactory
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory
|
||||
from xmodule.modulestore import XML_MODULESTORE_TYPE
|
||||
|
||||
from mock import patch
|
||||
|
||||
|
||||
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
|
||||
class TestInstructorDashboardEmailView(ModuleStoreTestCase):
|
||||
"""
|
||||
Check for email view displayed with flag
|
||||
"""
|
||||
def setUp(self):
|
||||
self.course = CourseFactory.create()
|
||||
|
||||
# Create instructor account
|
||||
instructor = AdminFactory.create()
|
||||
self.client.login(username=instructor.username, password="test")
|
||||
|
||||
# URL for instructor dash
|
||||
self.url = reverse('instructor_dashboard', kwargs={'course_id': self.course.id})
|
||||
# URL for email view
|
||||
self.email_link = '<a href="#" onclick="goto(\'Email\')" class="None">Email</a>'
|
||||
|
||||
def tearDown(self):
|
||||
"""
|
||||
Undo all patches.
|
||||
"""
|
||||
patch.stopall()
|
||||
|
||||
@patch.dict(settings.MITX_FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': True})
|
||||
def test_email_flag_true(self):
|
||||
# Assert that the URL for the email view is in the response
|
||||
response = self.client.get(self.url)
|
||||
self.assertTrue(self.email_link in response.content)
|
||||
|
||||
# Select the Email view of the instructor dash
|
||||
session = self.client.session
|
||||
session['idash_mode'] = 'Email'
|
||||
session.save()
|
||||
response = self.client.get(self.url)
|
||||
|
||||
# Ensure we've selected the view properly and that the send_to field is present.
|
||||
selected_email_link = '<a href="#" onclick="goto(\'Email\')" class="selectedmode">Email</a>'
|
||||
self.assertTrue(selected_email_link in response.content)
|
||||
send_to_label = '<label for="id_to">Send to:</label>'
|
||||
self.assertTrue(send_to_label in response.content)
|
||||
|
||||
@patch.dict(settings.MITX_FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': False})
|
||||
def test_email_flag_false(self):
|
||||
# Assert that the URL for the email view is not in the response
|
||||
response = self.client.get(self.url)
|
||||
self.assertFalse(self.email_link in response.content)
|
||||
|
||||
@patch.dict(settings.MITX_FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': True})
|
||||
def test_email_flag_true_xml_store(self):
|
||||
# If the enable email setting is enabled, but this is an XML backed course,
|
||||
# the email view shouldn't be available on the instructor dashboard.
|
||||
|
||||
# The course factory uses a MongoModuleStore backing, so patch the
|
||||
# `get_modulestore_type` method to pretend to be XML-backed.
|
||||
# This is OK; we're simply testing that the `is_mongo_modulestore_type` flag
|
||||
# in `instructor/views/legacy.py` is doing the correct thing.
|
||||
|
||||
with patch('xmodule.modulestore.mongo.base.MongoModuleStore.get_modulestore_type') as mock_modulestore:
|
||||
mock_modulestore.return_value = XML_MODULESTORE_TYPE
|
||||
|
||||
# Assert that the URL for the email view is not in the response
|
||||
response = self.client.get(self.url)
|
||||
self.assertFalse(self.email_link in response.content)
|
||||
|
||||
|
||||
@override_settings(MODULESTORE=TEST_DATA_MONGO_MODULESTORE)
|
||||
class TestStudentDashboardEmailView(ModuleStoreTestCase):
|
||||
"""
|
||||
Check for email view displayed with flag
|
||||
"""
|
||||
def setUp(self):
|
||||
self.course = CourseFactory.create()
|
||||
|
||||
# Create student account
|
||||
student = UserFactory.create()
|
||||
CourseEnrollmentFactory.create(user=student, course_id=self.course.id)
|
||||
self.client.login(username=student.username, password="test")
|
||||
|
||||
# URL for dashboard
|
||||
self.url = reverse('dashboard')
|
||||
# URL for email settings modal
|
||||
self.email_modal_link = (('<a href="#email-settings-modal" class="email-settings" rel="leanModal" '
|
||||
'data-course-id="{0}/{1}/{2}" data-course-number="{1}" '
|
||||
'data-optout="False">Email Settings</a>')
|
||||
.format(self.course.org,
|
||||
self.course.number,
|
||||
self.course.display_name.replace(' ', '_')))
|
||||
|
||||
def tearDown(self):
|
||||
"""
|
||||
Undo all patches.
|
||||
"""
|
||||
patch.stopall()
|
||||
|
||||
@patch.dict(settings.MITX_FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': True})
|
||||
def test_email_flag_true(self):
|
||||
# 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.MITX_FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': False})
|
||||
def test_email_flag_false(self):
|
||||
# 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)
|
||||
|
||||
@patch.dict(settings.MITX_FEATURES, {'ENABLE_INSTRUCTOR_EMAIL': True})
|
||||
def test_email_flag_true_xml_store(self):
|
||||
# If the enable email setting is enabled, but this is an XML backed course,
|
||||
# the email view shouldn't be available on the instructor dashboard.
|
||||
|
||||
# The course factory uses a MongoModuleStore backing, so patch the
|
||||
# `get_modulestore_type` method to pretend to be XML-backed.
|
||||
# This is OK; we're simply testing that the `is_mongo_modulestore_type` flag
|
||||
# in `instructor/views/legacy.py` is doing the correct thing.
|
||||
|
||||
with patch('xmodule.modulestore.mongo.base.MongoModuleStore.get_modulestore_type') as mock_modulestore:
|
||||
mock_modulestore.return_value = XML_MODULESTORE_TYPE
|
||||
|
||||
# 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)
|
||||
@@ -98,7 +98,7 @@ def require_query_params(*args, **kwargs):
|
||||
for (param, extra) in required_params:
|
||||
default = object()
|
||||
if request.GET.get(param, default) == default:
|
||||
error_response_data['parameters'] += [param]
|
||||
error_response_data['parameters'].append(param)
|
||||
error_response_data['info'][param] = extra
|
||||
|
||||
if len(error_response_data['parameters']) > 0:
|
||||
@@ -108,14 +108,14 @@ def require_query_params(*args, **kwargs):
|
||||
return wrapped
|
||||
return decorator
|
||||
|
||||
|
||||
def require_post_params(*args, **kwargs):
|
||||
"""
|
||||
Checks for required paremters or renders a 400 error.
|
||||
Checks for required parameters or renders a 400 error.
|
||||
(decorator with arguments)
|
||||
|
||||
`args` is a *list of required GET parameter names.
|
||||
`kwargs` is a **dict of required GET parameter names
|
||||
to string explanations of the parameter
|
||||
Functions like 'require_query_params', but checks for
|
||||
POST parameters rather than GET parameters.
|
||||
"""
|
||||
required_params = []
|
||||
required_params += [(arg, None) for arg in args]
|
||||
@@ -135,7 +135,7 @@ def require_post_params(*args, **kwargs):
|
||||
for (param, extra) in required_params:
|
||||
default = object()
|
||||
if request.POST.get(param, default) == default:
|
||||
error_response_data['parameters'] += [param]
|
||||
error_response_data['parameters'].append(param)
|
||||
error_response_data['info'][param] = extra
|
||||
|
||||
if len(error_response_data['parameters']) > 0:
|
||||
@@ -755,10 +755,11 @@ def send_email(request, course_id):
|
||||
Send an email to self, staff, or everyone involved in a course.
|
||||
Query Parameters:
|
||||
- 'send_to' specifies what group the email should be sent to
|
||||
Options are defined by the Email model in
|
||||
lms/djangoapps/bulk_email/models.py
|
||||
- 'subject' specifies email's subject
|
||||
- 'message' specifies email's content
|
||||
"""
|
||||
course = get_course_by_id(course_id)
|
||||
send_to = request.POST.get("send_to")
|
||||
subject = request.POST.get("subject")
|
||||
message = request.POST.get("message")
|
||||
@@ -769,52 +770,11 @@ def send_email(request, course_id):
|
||||
to_option=send_to,
|
||||
subject=subject,
|
||||
html_message=message,
|
||||
text_message=text_message
|
||||
text_message=text_message,
|
||||
)
|
||||
email.save()
|
||||
tasks.delegate_email_batches.delay(
|
||||
email.id,
|
||||
request.user.id
|
||||
)
|
||||
response_payload = {
|
||||
'course_id': course_id,
|
||||
}
|
||||
return JsonResponse(response_payload)
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_post_params(send_to="sending to whom", subject="subject line", message="message text")
|
||||
def send_email(request, course_id):
|
||||
"""
|
||||
Send an email to self, staff, or everyone involved in a course.
|
||||
Query Parameters:
|
||||
- 'send_to' specifies what group the email should be sent to
|
||||
- 'subject' specifies email's subject
|
||||
- 'message' specifies email's content
|
||||
"""
|
||||
course = get_course_by_id(course_id)
|
||||
send_to = request.POST.get("send_to")
|
||||
subject = request.POST.get("subject")
|
||||
message = request.POST.get("message")
|
||||
text_message = html_to_text(message)
|
||||
email = CourseEmail(
|
||||
course_id=course_id,
|
||||
sender=request.user,
|
||||
to_option=send_to,
|
||||
subject=subject,
|
||||
html_message=message,
|
||||
text_message=text_message
|
||||
)
|
||||
email.save()
|
||||
tasks.delegate_email_batches.delay(
|
||||
email.id,
|
||||
request.user.id
|
||||
)
|
||||
response_payload = {
|
||||
'course_id': course_id,
|
||||
}
|
||||
tasks.delegate_email_batches.delay(email.id, request.user.id) # pylint: disable=E1101
|
||||
response_payload = {'course_id': course_id}
|
||||
return JsonResponse(response_payload)
|
||||
|
||||
|
||||
|
||||
@@ -47,11 +47,10 @@ def instructor_dashboard_2(request, course_id):
|
||||
_section_membership(course_id, access),
|
||||
_section_student_admin(course_id, access),
|
||||
_section_data_download(course_id),
|
||||
_section_analytics(course_id)
|
||||
_section_analytics(course_id),
|
||||
]
|
||||
|
||||
enrollment_count = sections[0]['enrollment_count']
|
||||
|
||||
disable_buttons = False
|
||||
max_enrollment_for_buttons = settings.MITX_FEATURES.get("MAX_ENROLLMENT_INSTR_BUTTONS")
|
||||
if max_enrollment_for_buttons is not None:
|
||||
@@ -160,14 +159,14 @@ def _section_data_download(course_id):
|
||||
def _section_send_email(course_id, access, course):
|
||||
""" Provide data for the corresponding bulk email section """
|
||||
html_module = HtmlDescriptor(course.system, DictFieldData({'data': ''}), ScopeIds(None, None, None, None))
|
||||
fragment = course.system.render(html_module, None, 'studio_view')
|
||||
fragment = course.system.render(html_module, 'studio_view')
|
||||
fragment = wrap_xmodule('xmodule_edit.html', html_module, 'studio_view', fragment, None)
|
||||
email_editor = fragment.content
|
||||
section_data = {
|
||||
'section_key': 'send_email',
|
||||
'section_display_name': _('Email'),
|
||||
'access': access,
|
||||
'send_email': reverse('send_email',kwargs={'course_id': course_id}),
|
||||
'access': access,
|
||||
'send_email': reverse('send_email', kwargs={'course_id': course_id}),
|
||||
'editor': email_editor
|
||||
}
|
||||
return section_data
|
||||
|
||||
@@ -74,6 +74,14 @@ DATABASES = {
|
||||
}
|
||||
}
|
||||
|
||||
TRACKING_BACKENDS.update({
|
||||
'mongo': {
|
||||
'ENGINE': 'track.backends.mongodb.MongoBackend'
|
||||
}
|
||||
})
|
||||
|
||||
DEFAULT_BULK_FROM_EMAIL = "test@test.org"
|
||||
|
||||
# Forums are disabled in test.py to speed up unit tests, but we do not have
|
||||
# per-test control for acceptance tests
|
||||
MITX_FEATURES['ENABLE_DISCUSSION_SERVICE'] = True
|
||||
@@ -111,7 +119,7 @@ FEEDBACK_SUBMISSION_EMAIL = 'dummy@example.com'
|
||||
|
||||
# Include the lettuce app for acceptance testing, including the 'harvest' django-admin command
|
||||
INSTALLED_APPS += ('lettuce.django',)
|
||||
LETTUCE_APPS = ('courseware',)
|
||||
LETTUCE_APPS = ('courseware', 'instructor',)
|
||||
LETTUCE_BROWSER = os.environ.get('LETTUCE_BROWSER', 'chrome')
|
||||
|
||||
# Where to run: local, saucelabs, or grid
|
||||
|
||||
@@ -114,7 +114,7 @@ MITX_FEATURES = {
|
||||
# analytics experiments
|
||||
'ENABLE_INSTRUCTOR_ANALYTICS': False,
|
||||
|
||||
'ENABLE_INSTRUCTOR_EMAIL': True,
|
||||
'ENABLE_INSTRUCTOR_EMAIL': False,
|
||||
|
||||
# enable analytics server.
|
||||
# WARNING: THIS SHOULD ALWAYS BE SET TO FALSE UNDER NORMAL
|
||||
|
||||
@@ -27,9 +27,15 @@ class SendEmail
|
||||
else if @$emailEditor.save()['data'] == ""
|
||||
alert gettext("Your message cannot be blank.")
|
||||
else
|
||||
success_message = gettext("Your email was successfully queued for sending.")
|
||||
send_to = @$send_to.val().toLowerCase()
|
||||
if send_to == "myself"
|
||||
send_to = gettext("yourself")
|
||||
else if send_to == "staff"
|
||||
send_to = gettext("everyone who is staff or instructor on this course")
|
||||
else
|
||||
send_to = gettext("ALL (everyone who is enrolled in this course as student, staff, or instructor)")
|
||||
success_message = gettext("Your email was successfully queued for sending. Please note that for large public classes (~10k), it may take 1-2 hours to send all emails.")
|
||||
subject = gettext(@$subject.val())
|
||||
confirm_message = gettext("You are about to send an email titled \"#{subject}\" to #{send_to}. Is this OK?")
|
||||
if confirm confirm_message
|
||||
@@ -46,21 +52,27 @@ class SendEmail
|
||||
url: @$btn_send.data 'endpoint'
|
||||
data: send_data
|
||||
success: (data) =>
|
||||
@display_response gettext('Your email was successfully queued for sending.')
|
||||
$(".msg-confirm").css({"display":"block"})
|
||||
error: std_ajax_err => @fail_with_error gettext('Error sending email.')
|
||||
$(".msg-confirm").css({"display":"none"})
|
||||
@display_response success_message
|
||||
|
||||
error: std_ajax_err =>
|
||||
@fail_with_error gettext('Error sending email.')
|
||||
|
||||
else
|
||||
@$task_response.empty()
|
||||
@$request_response_error.empty()
|
||||
|
||||
fail_with_error: (msg) ->
|
||||
console.warn msg
|
||||
@$task_response.empty()
|
||||
@$request_response_error.empty()
|
||||
@$request_response_error.text gettext(msg)
|
||||
$(".msg-confirm").css({"display":"none"})
|
||||
|
||||
display_response: (data_from_server) ->
|
||||
@$task_response.empty()
|
||||
@$request_response_error.empty()
|
||||
@$task_response.text(gettext('Your email was successfully queued for sending.'))
|
||||
@$task_response.text(data_from_server)
|
||||
$(".msg-confirm").css({"display":"block"})
|
||||
|
||||
|
||||
# Email Section
|
||||
|
||||
@@ -22,8 +22,9 @@
|
||||
// system feedback - messages
|
||||
.msg {
|
||||
border-radius: 1px;
|
||||
padding: 10px 15px;
|
||||
margin-bottom: 20px;
|
||||
padding: $baseline/2 $baseline*0.75;
|
||||
margin-bottom: $baseline;
|
||||
font-weight: 600;
|
||||
|
||||
.copy {
|
||||
font-weight: 600;
|
||||
@@ -44,10 +45,8 @@
|
||||
.msg-confirm {
|
||||
border-top: 2px solid $confirm-color;
|
||||
background: tint($confirm-color,95%);
|
||||
|
||||
.copy {
|
||||
color: $confirm-color;
|
||||
}
|
||||
display: none;
|
||||
color: $confirm-color;
|
||||
}
|
||||
|
||||
// TYPE: confirm
|
||||
@@ -76,7 +75,7 @@
|
||||
.list-advice {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 20px 0;
|
||||
margin: $baseline 0;
|
||||
|
||||
.item {
|
||||
font-weight: 600;
|
||||
@@ -249,7 +248,7 @@ section.instructor-dashboard-content-2 {
|
||||
padding: 0;
|
||||
|
||||
.field {
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: $baseline;
|
||||
padding: 0;
|
||||
|
||||
&:last-child {
|
||||
@@ -257,43 +256,6 @@ section.instructor-dashboard-content-2 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// system feedback - messages
|
||||
.msg {
|
||||
border-radius: 1px;
|
||||
padding: 10px 15px;
|
||||
margin-bottom: 20px;
|
||||
font-weight: 600;
|
||||
color: green;
|
||||
|
||||
.copy {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
.msg-confirm {
|
||||
background: tint(green,90%);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.list-advice {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 20px 0;
|
||||
|
||||
.item {
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.copy {
|
||||
font-weight: 600; }
|
||||
.msg-confirm {
|
||||
background: #e5f2e5; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -174,7 +174,7 @@ function goto( mode)
|
||||
|
||||
<p>
|
||||
<input type="submit" name="action" value="Download CSV of answer distributions" class="${'is-disabled' if disable_buttons else ''}">
|
||||
<input type="submit" name="action" value="Dump description of graded assignments configuration" class="${'is-disabled' if disable_buttons else ''}">
|
||||
<input type="submit" name="action" value="Dump description of graded assignments configuration">
|
||||
</p>
|
||||
<hr width="40%" style="align:left">
|
||||
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%page args="section_data"/>
|
||||
|
||||
<script language="JavaScript" type="text/javascript">
|
||||
</script>
|
||||
<script type="text/javascript" src="jsi18n/"></script>
|
||||
<div class="vert-left send-email">
|
||||
<div class="vert-left send-email" id="section-send-email">
|
||||
<h2> ${_("Send Email")} </h2>
|
||||
<div class="request-response msg msg-confirm copy" id="request-response"></div>
|
||||
<ul class="list-fields">
|
||||
|
||||
Reference in New Issue
Block a user