Commit a87667af by Awais

Implementing emails functionality for course, course-run and seat.

On each comment send the email to course-related group users.
Adding test cases and methods.

ECOM-5929
parent 5b180b47
from django.db import migrations
def create_switch(apps, schema_editor):
"""Create the enable_publisher_email_notifications switch if it does not already exist."""
Switch = apps.get_model('waffle', 'Switch')
Switch.objects.get_or_create(name='enable_publisher_email_notifications', defaults={'active': False})
def delete_switch(apps, schema_editor):
"""Delete the enable_publisher_email_notifications switch."""
Switch = apps.get_model('waffle', 'Switch')
Switch.objects.filter(name='enable_publisher_email_notifications').delete()
class Migration(migrations.Migration):
dependencies = [
('publisher', '0012_auto_20161020_0718'),
('waffle', '0001_initial'),
]
operations = [
migrations.RunPython(create_switch, delete_switch),
]
......@@ -41,7 +41,6 @@ class LoginRequiredMixin(object):
class FormValidMixin(object):
change_state = False
assign_user_groups = False
def form_valid(self, form):
user = self.request.user
......@@ -49,9 +48,6 @@ class FormValidMixin(object):
publisher_object.changed_by = user
publisher_object.save()
if self.assign_user_groups:
publisher_object.assign_user_groups(user)
if self.change_state:
publisher_object.change_state(user=user)
......
......@@ -7,7 +7,7 @@ from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _
from django_extensions.db.models import TimeStampedModel
from django_fsm import FSMField, transition
from guardian.shortcuts import assign_perm, get_groups_with_perms
from guardian.shortcuts import assign_perm, get_groups_with_perms, get_users_with_perms
from simple_history.models import HistoricalRecords
from sortedm2m.fields import SortedManyToManyField
from stdimage.models import StdImageField
......@@ -147,19 +147,30 @@ class Course(TimeStampedModel, ChangedByMixin):
('view_course', 'Can view course'),
)
def assign_user_groups(self, user):
for group in user.groups.all():
assign_perm(self.VIEW_PERMISSION, group, self)
def assign_permission_by_group(self, institution):
""" Assigns permission on the course against the group. """
assign_perm(self.VIEW_PERMISSION, institution, self)
@property
def get_group_institution(self):
""" Returns the Group object with for the given course object. """
def group_institution(self):
""" Returns the group object having permissions on the given course.
Course will be associated with one group only.
"""
available_groups = get_groups_with_perms(self)
return available_groups[0] if available_groups else None
def get_group_users_emails(self):
""" Returns the list of users emails with enable email notifications
against a course group. By default if attribute value does not exists
then user will be eligible for emails.
"""
users_list = get_users_with_perms(self)
emails = [
user.email for user in users_list
if not hasattr(user, 'attributes') or user.attributes.enable_email_notification
]
return emails
@property
def keywords_data(self):
keywords = self.keywords.all()
......
......@@ -62,6 +62,14 @@ class CourseTests(TestCase):
self.course = factories.CourseFactory()
self.course2 = factories.CourseFactory()
self.user1 = UserFactory()
self.user2 = UserFactory()
self.user3 = UserFactory()
self.group_a = factories.GroupFactory()
self.group_b = factories.GroupFactory()
self.user1.groups.add(self.group_a)
self.user2.groups.add(self.group_b)
def test_str(self):
""" Verify casting an instance to a string returns a string containing the course title. """
self.assertEqual(str(self.course), self.course.title)
......@@ -72,25 +80,48 @@ class CourseTests(TestCase):
reverse('publisher:publisher_courses_edit', kwargs={'pk': self.course.id})
)
def test_assign_user_groups(self):
user1 = UserFactory()
user2 = UserFactory()
group_a = factories.GroupFactory(name="Test Group A")
group_b = factories.GroupFactory(name="Test Group B")
user1.groups.add(group_a)
user2.groups.add(group_b)
def test_assign_permission_by_group(self):
""" Verify that permission can be assigned using the group. """
self.assert_user_cannot_view_course(self.user1, self.course)
self.assert_user_cannot_view_course(self.user2, self.course2)
self.course.assign_permission_by_group(self.group_a)
self.course2.assign_permission_by_group(self.group_b)
self.assert_user_can_view_course(self.user1, self.course)
self.assert_user_can_view_course(self.user2, self.course2)
self.assert_user_cannot_view_course(self.user1, self.course2)
self.assert_user_cannot_view_course(self.user2, self.course)
self.assertEqual(self.course.group_institution, self.group_a)
self.assertEqual(self.course2.group_institution, self.group_b)
def assert_user_cannot_view_course(self, user, course):
""" Asserts the user can NOT view the course. """
self.assertFalse(user.has_perm(Course.VIEW_PERMISSION, course))
self.assertFalse(user1.has_perm(Course.VIEW_PERMISSION, self.course))
self.assertFalse(user2.has_perm(Course.VIEW_PERMISSION, self.course2))
def assert_user_can_view_course(self, user, course):
""" Asserts the user can view the course. """
self.assertTrue(user.has_perm(Course.VIEW_PERMISSION, course))
self.course.assign_user_groups(user1)
self.course2.assign_user_groups(user2)
def test_group_institution(self):
""" Verify the method returns groups permitted to access the course."""
self.assertEqual(self.course.group_institution, None)
self.course.assign_permission_by_group(self.group_a)
self.assertEqual(self.course.group_institution, self.group_a)
self.assertTrue(user1.has_perm(Course.VIEW_PERMISSION, self.course))
self.assertTrue(user2.has_perm(Course.VIEW_PERMISSION, self.course2))
def test_get_group_users_emails(self):
""" Verify the method returns the email addresses of users who are
permitted to access the course AND have not disabled email notifications.
"""
self.user3.groups.add(self.group_a)
self.course.assign_permission_by_group(self.group_a)
self.assertListEqual(self.course.get_group_users_emails(), [self.user1.email, self.user3.email])
self.assertFalse(user1.has_perm(Course.VIEW_PERMISSION, self.course2))
self.assertFalse(user2.has_perm(Course.VIEW_PERMISSION, self.course))
# The email addresses of users who have disabled email notifications should NOT be returned.
factories.UserAttributeFactory(user=self.user1, enable_email_notification=False)
self.assertListEqual(self.course.get_group_users_emails(), [self.user3.email])
def test_keywords_data(self):
""" Verify that the property returns the keywords as comma separated string. """
......
......@@ -275,7 +275,7 @@ class CreateUpdateCourseViewTests(TestCase):
status_code=302,
target_status_code=200
)
self.assertEqual(course.get_group_institution, self.group)
self.assertEqual(course.group_institution, self.group)
self.assertEqual(course.team_admin, self.user)
self.assertTrue(self.user.has_perm(Course.VIEW_PERMISSION, course))
course_run = course.publisher_course_runs.all()[0]
......
......@@ -12,7 +12,6 @@ urlpatterns = [
url(r'^course_runs/(?P<pk>\d+)/$', views.CourseRunDetailView.as_view(), name='publisher_course_run_detail'),
url(r'^course_runs/$', views.CourseRunListView.as_view(), name='publisher_course_runs'),
url(r'^course_runs/new$', views.CreateCourseRunView.as_view(), name='publisher_course_runs_new'),
url(r'^course_runs/(?P<pk>\d+)/$', views.CourseRunDetailView.as_view(), name='publisher_course_run_detail'),
url(r'^course_runs/(?P<pk>\d+)/edit/$', views.UpdateCourseRunView.as_view(), name='publisher_course_runs_edit'),
url(
r'^course_runs/(?P<course_run_id>\d+)/change_state/$',
......
import logging
from django.conf import settings
from django.core.mail.message import EmailMultiAlternatives
from django.core.urlresolvers import reverse
from django.template.loader import get_template
from django.utils.translation import ugettext_lazy as _
from course_discovery.apps.publisher.models import CourseRun
log = logging.getLogger(__name__)
def send_email_for_comment(comment):
""" Send the emails for a comment.
Arguments:
comment (Comment): Comment object
"""
try:
object_pk = comment.object_pk
publisher_obj = comment.content_type.get_object_for_this_type(pk=object_pk)
comment_class = comment.content_type.model_class()
if comment_class == CourseRun:
course = publisher_obj.course
object_path = reverse('publisher:publisher_course_run_detail', args=[publisher_obj.id])
subject = _('New comment added in course run: {title}-{pacing_type}-{start}').format(
title=course.title,
pacing_type=publisher_obj.get_pacing_type_display(),
start=publisher_obj.start.strftime('%B %d, %Y') if publisher_obj.start else ''
)
else:
course = publisher_obj
object_path = reverse('publisher:publisher_courses_edit', args=[publisher_obj.id])
subject = _('New comment added in Course: {title}').format(title=course.title)
to_addresses = course.get_group_users_emails()
from_address = settings.PUBLISHER_FROM_EMAIL
context = {
'comment': comment,
'course': course,
'object_type': comment_class.__name__,
'page_url': 'https://{host}{path}'.format(host=comment.site.domain.strip('/'), path=object_path)
}
txt_template = 'publisher/email/comment.txt'
html_template = 'publisher/email/comment.html'
template = get_template(txt_template)
plain_content = template.render(context)
template = get_template(html_template)
html_content = template.render(context)
email_msg = EmailMultiAlternatives(
subject, plain_content, from_address, to_addresses
)
email_msg.attach_alternative(html_content, 'text/html')
email_msg.send()
except Exception: # pylint: disable=broad-except
log.exception('Failed to send email notifications for comment %s', comment.id)
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _
from django_comments.models import CommentAbstractModel
from django_extensions.db.fields import ModificationDateTimeField
import waffle
from course_discovery.apps.publisher_comments.emails import send_email_for_comment
class Comments(CommentAbstractModel):
modified = ModificationDateTimeField(_('modified'))
@receiver(post_save, sender=Comments)
def send_email(sender, instance, **kwargs): # pylint: disable=unused-argument
""" Send email on new comment. """
if waffle.switch_is_active('enable_publisher_email_notifications'):
send_email_for_comment(instance)
import ddt
import mock
from django.conf import settings
from django.contrib.sites.models import Site
from django.core import mail
from django.core.urlresolvers import reverse
from django.test import TestCase
from guardian.shortcuts import assign_perm
from course_discovery.apps.core.tests.factories import UserFactory
from course_discovery.apps.course_metadata.tests import toggle_switch
from course_discovery.apps.publisher.models import Course
from course_discovery.apps.publisher.tests import factories
from course_discovery.apps.publisher.tests.factories import UserAttributeFactory
from course_discovery.apps.publisher_comments.tests.factories import CommentFactory
@ddt.ddt
class CommentsEmailTests(TestCase):
""" Tests for the e-mail functionality for course, course-run and seats. """
def setUp(self):
super(CommentsEmailTests, self).setUp()
self.user = UserFactory()
self.user_2 = UserFactory()
self.user_3 = UserFactory()
self.site = Site.objects.get(pk=settings.SITE_ID)
self.group = factories.GroupFactory(name='abc')
self.user.groups.add(self.group)
self.user_2.groups.add(self.group)
self.user_3.groups.add(self.group)
self.seat = factories.SeatFactory()
self.course_run = self.seat.course_run
self.course = self.course_run.course
assign_perm(Course.VIEW_PERMISSION, self.group, self.course)
# NOTE: We intentionally do NOT create an attribute for user_2.
# By default this user WILL receive email notifications.
UserAttributeFactory(user=self.user, enable_email_notification=True)
UserAttributeFactory(user=self.user_3, enable_email_notification=False)
toggle_switch('enable_publisher_email_notifications', True)
def test_course_comment_email(self):
""" Verify that after adding a comment against a course emails send
to multiple users depending upon the course related group.
"""
comment = self.create_comment(content_object=self.course)
subject = 'New comment added in Course: {title}'.format(title=self.course.title)
self.assert_comment_email_sent(
self.course, comment, reverse('publisher:publisher_courses_edit', args=[self.course.id]),
subject
)
def test_course_run_comment_email(self):
""" Verify that after adding a comment against a course-run emails send to multiple users
depending upon the parent course related group.
"""
comment = self.create_comment(content_object=self.course_run)
subject = 'New comment added in course run: {title}-{pacing_type}-{start}'.format(
title=self.course_run.course.title,
pacing_type=self.course_run.get_pacing_type_display(),
start=self.course_run.start.strftime('%B %d, %Y')
)
self.assert_comment_email_sent(
self.course_run, comment,
reverse('publisher:publisher_course_run_detail', args=[self.course_run.id]),
subject
)
@mock.patch('course_discovery.apps.publisher_comments.models.send_email_for_comment')
def test_email_with_enable_waffle_switch(self, send_email_for_comment):
""" Verify that send_email_for_comment called with enable waffle switch.. """
comment = self.create_comment(content_object=self.course)
send_email_for_comment.assert_called_once_with(comment)
@mock.patch('course_discovery.apps.publisher_comments.models.send_email_for_comment')
def test_email_with_disable_waffle_switch(self, send_email_for_comment):
""" Verify that send_email_for_comment not called with disable waffle switch.. """
toggle_switch('enable_publisher_email_notifications', False)
self.create_comment(content_object=self.course)
send_email_for_comment.assert_not_called()
def test_email_without_different_group(self):
""" Verify the emails behaviour if course group has no users. """
self.user.groups.remove(self.group)
self.user_2.groups.remove(self.group)
self.user_3.groups.remove(self.group)
self.create_comment(content_object=self.course)
self.assertEqual(len(mail.outbox), 0)
def test_course_run_without_start_date(self):
""" Verify that emails works properly even if course-run does not have a start date."""
self.course_run.start = None
self.course_run.save()
comment = self.create_comment(content_object=self.course_run)
subject = 'New comment added in course run: {title}-{pacing_type}-{start}'.format(
title=self.course_run.course.title,
pacing_type=self.course_run.get_pacing_type_display(),
start=''
)
self.assert_comment_email_sent(
self.course_run, comment,
reverse('publisher:publisher_course_run_detail', args=[self.course_run.id]),
subject
)
def assert_comment_email_sent(self, content_object, comment, object_path, subject):
""" DRY method to assert send email data"""
object_type = content_object.__class__.__name__
self.assertEqual([self.user.email, self.user_2.email], mail.outbox[0].to)
self.assertEqual(str(mail.outbox[0].subject), subject)
body = mail.outbox[0].body.strip()
heading = '{first_name} commented on a {object_type} belonging to the course {title} ({number})'
self.assertIn(
heading.format(
first_name=comment.user.first_name, object_type=object_type.lower(),
title=self.course.title, number=self.course.number
),
body
)
page_url = 'https://{host}{path}'.format(host=comment.site.domain.strip('/'), path=object_path)
self.assertIn(comment.comment, body)
self.assertIn(page_url, body)
self.assertIn('The edX team', body)
def create_comment(self, content_object):
""" DRY method to create the comment for a given content type."""
return CommentFactory(
content_object=content_object, user=self.user, site=self.site
)
from django.conf import settings
from django.contrib.sites.models import Site
from django.core import mail
from django.core.urlresolvers import reverse
from django.forms import model_to_dict
from django.test import TestCase
from course_discovery.apps.core.tests.factories import UserFactory, USER_PASSWORD
from course_discovery.apps.course_metadata.tests import toggle_switch
from course_discovery.apps.publisher.models import Seat
from course_discovery.apps.publisher.tests import factories
from course_discovery.apps.publisher_comments.tests.factories import CommentFactory
......@@ -16,15 +18,22 @@ class CommentsTests(TestCase):
def setUp(self):
super(CommentsTests, self).setUp()
self.user = UserFactory(is_staff=True, is_superuser=True)
self.group = factories.GroupFactory()
self.user.groups.add(self.group)
self.client.login(username=self.user.username, password=USER_PASSWORD)
self.site = Site.objects.get(pk=settings.SITE_ID)
self.course_edit_page = 'publisher:publisher_courses_edit'
self.course_run_edit_page = 'publisher:publisher_course_runs_edit'
self.seat_edit_page = 'publisher:publisher_seats_edit'
self.edit_comment_page = 'publisher_comments:comment_edit'
self.course = factories.CourseFactory()
self.course_run = factories.CourseRunFactory()
self.seat = factories.SeatFactory(type=Seat.PROFESSIONAL, credit_hours=0)
self.course_run = self.seat.course_run
self.course = self.course_run.course
self.course.assign_permission_by_group(self.group)
toggle_switch('enable_publisher_email_notifications', True)
def test_course_edit_page_with_multiple_comments(self):
""" Verify course edit page can load multiple comments"""
......@@ -34,24 +43,6 @@ class CommentsTests(TestCase):
""" Verify course-run edit page can load multiple comments"""
self._add_assert_multiple_comments(self.course_run, self.course_run_edit_page)
def test_seat_edit_page_with_multiple_comments(self):
""" Verify seat edit page can load multiple comments"""
self._add_assert_multiple_comments(self.seat, self.seat_edit_page)
def _add_assert_multiple_comments(self, content_object, page_path):
""" DRY method to add comments on edit page for specific object. """
response = self.client.get(reverse(page_path, kwargs={'pk': content_object.id}))
self.assertContains(response, 'Total Comments 0')
comments = []
for num in range(1, 10): # pylint: disable=unused-variable
comments.append(self._generate_comment(content_object=content_object, user=self.user))
response = self.client.get(reverse(page_path, kwargs={'pk': content_object.id}))
for comment in comments:
self.assertContains(response, comment.comment)
self.assertContains(response, 'Total Comments 9')
def test_comment_edit_with_course(self):
""" Verify that only comments attached with specific course appears on edited page. """
comments = self._generate_comments_for_all_content_types()
......@@ -94,6 +85,15 @@ class CommentsTests(TestCase):
self.seat, reverse(self.seat_edit_page, kwargs={'pk': self.seat.id})
)
def test_mail_outbox_count(self):
""" Verify that separate emails send for adding and editing the comment . """
self._edit_comment_page(
self.course, reverse(self.course_edit_page, kwargs={'pk': self.course.id})
)
# mail has 2 emails one due to newly added comment and other is due to editing.
self.assertEqual(len(mail.outbox), 2)
def test_edit_comment_of_other_user(self):
""" Verify that comment can be edited by the comment author only. """
comment = self._generate_comment(content_object=self.course, user=self.user)
......@@ -150,3 +150,21 @@ class CommentsTests(TestCase):
data[content] = self._generate_comment(content_object=content, user=self.user)
return data
def _add_assert_multiple_comments(self, content_object, page_path):
""" DRY method to add comments on edit page for specific object. """
response = self.client.get(reverse(page_path, kwargs={'pk': content_object.id}))
self.assertContains(response, 'Total Comments 0')
comments = []
for __ in range(1, 2):
comments.append(self._generate_comment(content_object=content_object, user=self.user))
# assert emails send
self.assertEqual(len(mail.outbox), 1)
response = self.client.get(reverse(page_path, kwargs={'pk': content_object.id}))
for comment in comments:
self.assertContains(response, comment.comment)
self.assertContains(response, 'Total Comments 1')
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-10-25 11:39-0400\n"
"POT-Creation-Date: 2016-10-25 21:30+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......@@ -551,6 +551,16 @@ msgstr ""
msgid "Publish"
msgstr ""
#: apps/publisher_comments/emails.py
#, python-brace-format
msgid "New comment added in course run: {title}-{pacing_type}-{start}"
msgstr ""
#: apps/publisher_comments/emails.py
#, python-brace-format
msgid "New comment added in Course: {title}"
msgstr ""
#: apps/publisher_comments/models.py
msgid "modified"
msgstr ""
......@@ -605,7 +615,7 @@ msgid ""
"trademarks or trademarks of %(link_start)sedX Inc.%(link_end)s"
msgstr ""
#: templates/footer.html
#: templates/footer.html templates/footer.html.py
msgid "Powered by Open edX"
msgstr ""
......@@ -1446,6 +1456,27 @@ msgstr ""
msgid "Looks like you haven't publish a course yet"
msgstr ""
#: templates/publisher/email/comment.html
#: templates/publisher/email/comment.txt
#, python-format
msgid ""
"%(first_name)s commented on a %(object_type)s belonging to the course "
"%(title)s (%(number)s)."
msgstr ""
#: templates/publisher/email/comment.html
msgid "View comment"
msgstr ""
#: templates/publisher/email/comment.html
#: templates/publisher/email/comment.txt
msgid "The edX team"
msgstr ""
#: templates/publisher/email/comment.txt
msgid "View comment:"
msgstr ""
#: templates/publisher/seat_form.html
msgid "Seat Form"
msgstr ""
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-10-25 11:39-0400\n"
"POT-Creation-Date: 2016-10-25 21:30+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-10-25 10:56-0400\n"
"POT-Creation-Date: 2016-10-25 21:30+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......@@ -664,6 +664,19 @@ msgstr "Fïnälïzé Ⱡ'σяєм ιρѕυм ∂#"
msgid "Publish"
msgstr "Püßlïsh Ⱡ'σяєм ιρѕυм #"
#: apps/publisher_comments/emails.py
#, python-brace-format
msgid "New comment added in course run: {title}-{pacing_type}-{start}"
msgstr ""
"Néw çömmént äddéd ïn çöürsé rün: {title}-{pacing_type}-{start} Ⱡ'σяєм ιρѕυм "
"∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя #"
#: apps/publisher_comments/emails.py
#, python-brace-format
msgid "New comment added in Course: {title}"
msgstr ""
"Néw çömmént äddéd ïn Çöürsé: {title} Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тє#"
#: apps/publisher_comments/models.py
msgid "modified"
msgstr "mödïfïéd Ⱡ'σяєм ιρѕυм ∂#"
......@@ -721,7 +734,7 @@ msgstr ""
"trädémärks ör trädémärks öf %(link_start)sédX Ìnç.%(link_end)s Ⱡ'σяєм ιρѕυм "
"∂σł#"
#: templates/footer.html
#: templates/footer.html templates/footer.html.py
msgid "Powered by Open edX"
msgstr "Pöwéréd ßý Öpén édX Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт,#"
......@@ -1683,6 +1696,29 @@ msgstr ""
"Lööks lïké ýöü hävén't püßlïsh ä çöürsé ýét Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, "
"¢σηѕє¢тєтυя #"
#: templates/publisher/email/comment.html
#: templates/publisher/email/comment.txt
#, python-format
msgid ""
"%(first_name)s commented on a %(object_type)s belonging to the course "
"%(title)s (%(number)s)."
msgstr ""
"%(first_name)s çömméntéd ön ä %(object_type)s ßélöngïng tö thé çöürsé "
"%(title)s (%(number)s). Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α#"
#: templates/publisher/email/comment.html
msgid "View comment"
msgstr "Vïéw çömmént Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
#: templates/publisher/email/comment.html
#: templates/publisher/email/comment.txt
msgid "The edX team"
msgstr "Thé édX téäm Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
#: templates/publisher/email/comment.txt
msgid "View comment:"
msgstr "Vïéw çömmént: Ⱡ'σяєм ιρѕυм ∂σłσя ѕι#"
#: templates/publisher/seat_form.html
msgid "Seat Form"
msgstr "Séät Förm Ⱡ'σяєм ιρѕυм ∂σł#"
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-10-25 10:56-0400\n"
"POT-Creation-Date: 2016-10-25 21:30+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......
......@@ -379,6 +379,7 @@ TAGGIT_CASE_INSENSITIVE = True
SOLO_CACHE = 'default'
SOLO_CACHE_TIMEOUT = 3600
PUBLISHER_FROM_EMAIL = None
# Django Debug Toolbar settings
# http://django-debug-toolbar.readthedocs.org/en/latest/installation.html
......@@ -409,3 +410,4 @@ if os.environ.get('ENABLE_DJANGO_TOOLBAR', False):
'debug_toolbar.panels.redirects.RedirectsPanel',
'elastic_panel.panel.ElasticDebugPanel'
]
......@@ -23,3 +23,5 @@ DATABASES = {
JWT_AUTH['JWT_SECRET_KEY'] = 'course-discovery-jwt-secret-key'
LOGGING['handlers']['local'] = {'class': 'logging.NullHandler'}
PUBLISHER_FROM_EMAIL = 'test@example.com'
......@@ -4,7 +4,7 @@
<div class="comments-container">
{% get_comment_count for comment_object as comment_count %}
<h4 class="hd-4">
{% blocktrans trimmed with comment_count=comment_count %}
{% blocktrans with comment_count=comment_count trimmed %}
Total Comments {{ comment_count }}
{% endblocktrans %}
</h4>
......
{% extends "publisher/email/email_base.html" %}
{% load i18n %}
{% block body %}
<!-- Message Body -->
<p>
{% blocktrans with first_name=comment.user.first_name object_type=object_type|lower title=course.title number=course.number trimmed %}
{{ first_name }} commented on a {{ object_type }} belonging to the course {{ title }} ({{ number }}).
{% endblocktrans %}
</p>
<p>
{{ comment.comment }}
</p>
<p>
<a href="{{ page_url }}">{% trans "View comment" %}</a>
</p>
<p>{% trans "The edX team" %}</p>
<!-- End Message Body -->
{% endblock body %}
{% load i18n %}
{% blocktrans with first_name=comment.user.first_name object_type=object_type|lower title=course.title number=course.number trimmed %}
{{ first_name }} commented on a {{ object_type }} belonging to the course {{ title }} ({{ number }}).
{% endblocktrans %}
{{ comment.comment }}
{% trans "View comment:" %} {{ page_url }}
{% trans "The edX team" %}
{% load i18n %}
{% get_current_language as LANGUAGE_CODE %}
<!DOCTYPE html>
<html lang="{{ LANGUAGE_CODE }}">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="initial-scale=1.0"> <!-- So that mobile webkit will display zoomed in -->
<meta name="format-detection" content="telephone=no"> <!-- disable auto telephone linking in iOS -->
</head>
<body style="font-family:Arial,'Helvetica Neue',Helvetica,sans-serif;font-size:14px;line-height:150%;margin:auto">
<table border="0" width="100%" height="100%" cellpadding="0" cellspacing="0" style="padding: 5px;">
<tr>
<td align="" valign="top">
{% block body %}
{% endblock body %}
</td>
</tr>
</table>
</body>
</html>
Publisher
=========
The ``publisher`` tool is an information management system that supports the course authoring, review, and approval workflow. The tool manages courses as they transition through the lifecycle of creation (across various subsystems), release/publication to the edX.org marketing site, and eventual retirement/archival.
Note: This app is primarily built for the edX.org use case.
Configure Emails
----------------
The publisher tool supports notifying responsible parties when users comment on courses and course runs. Notifications are sent via Django's email backend. In order to send emails the ``enable_publisher_email_notifications`` switch must be activated, and the following settings must be defined:
+------------------------------------+----------------------------------------------------------------------------+--------------------------------------------------------------------------+
| Setting | Description | Value |
+====================================+============================================================================+==========================================================================+
| PUBLISHER_FROM_EMAIL | Official email address for sending emails. | Email address |
+------------------------------------+----------------------------------------------------------------------------+--------------------------------------------------------------------------+
Since the publisher tool uses the built-in Django email functionality, any Django email backend can be used. For info on configuring email backends see `django_email`_.
.. _django_email: https://docs.djangoproject.com/en/1.10/topics/email/
We at edX.org use Amazon's Simple Email Service (SES) (`amazon_ses`_). If you'd also like to use SES, the django-ses (`django_ses`_) is installed as a base requirement. Simply define values for the settings below to configure the backend.
.. _amazon_ses: https://aws.amazon.com/ses/
.. _django_ses: https://github.com/django-ses/django-ses
+------------------------------------+----------------------------------------------------------------------------+--------------------------------------------------------------------------+
| Setting | Description | Value |
+====================================+============================================================================+==========================================================================+
| EMAIL_BACKEND | django_ses.SESBackend | The backend to use for sending emails. |
+------------------------------------+----------------------------------------------------------------------------+--------------------------------------------------------------------------+
| AWS_ACCESS_KEY_ID | YOUR-ACCESS-KEY-ID | (This should be set to the value from the AWS account.) |
+------------------------------------+----------------------------------------------------------------------------+--------------------------------------------------------------------------+
| AWS_SECRET_ACCESS_KEY | YOUR-SECRET-ACCESS-KEY | (This should be set to the value from the AWS account.) |
+------------------------------------+----------------------------------------------------------------------------+--------------------------------------------------------------------------+
| AWS_SES_REGION_NAME | Region your SES service is using. | (This should be set to the value from the AWS account.) |
+------------------------------------+----------------------------------------------------------------------------+--------------------------------------------------------------------------+
| AWS_SES_REGION_ENDPOINT | Region your SES service is using. | (This should be set to the value from the AWS account.) |
+------------------------------------+----------------------------------------------------------------------------+--------------------------------------------------------------------------+
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment