Commit a564e3b5 by Awais Committed by Awais Qureshi

First basic code for new api.

Implement the decline functionality for approval flow.

ECOM-6330
parent 3a54e9bd
...@@ -93,7 +93,7 @@ class CourseRunSerializer(serializers.ModelSerializer): ...@@ -93,7 +93,7 @@ class CourseRunSerializer(serializers.ModelSerializer):
lms_course_id = validated_data.get('lms_course_id') lms_course_id = validated_data.get('lms_course_id')
if preview_url: if preview_url:
instance.course_run_state.change_role(PublisherUserRole.CourseTeam) instance.course_run_state.change_owner_role(PublisherUserRole.CourseTeam)
if waffle.switch_is_active('enable_publisher_email_notifications'): if waffle.switch_is_active('enable_publisher_email_notifications'):
if preview_url: if preview_url:
......
"""Tests API Serializers.""" """Tests API Serializers."""
from django.core import mail
from django.test import RequestFactory, TestCase from django.test import RequestFactory, TestCase
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
from course_discovery.apps.core.tests.factories import UserFactory from course_discovery.apps.core.tests.factories import UserFactory
from course_discovery.apps.core.tests.helpers import make_image_file from course_discovery.apps.core.tests.helpers import make_image_file
from course_discovery.apps.course_metadata.tests import toggle_switch
from course_discovery.apps.course_metadata.tests.factories import PersonFactory from course_discovery.apps.course_metadata.tests.factories import PersonFactory
from course_discovery.apps.ietf_language_tags.models import LanguageTag from course_discovery.apps.ietf_language_tags.models import LanguageTag
from course_discovery.apps.publisher.api.serializers import (CourseRevisionSerializer, CourseRunSerializer, from course_discovery.apps.publisher.api.serializers import (CourseRevisionSerializer, CourseRunSerializer,
CourseRunStateSerializer, CourseStateSerializer, CourseRunStateSerializer, CourseStateSerializer,
CourseUserRoleSerializer, GroupUserSerializer) CourseUserRoleSerializer, GroupUserSerializer)
from course_discovery.apps.publisher.choices import CourseRunStateChoices, CourseStateChoices, PublisherUserRole from course_discovery.apps.publisher.choices import CourseRunStateChoices, CourseStateChoices, PublisherUserRole
from course_discovery.apps.publisher.models import CourseState, Seat from course_discovery.apps.publisher.models import CourseRun, CourseState, Seat
from course_discovery.apps.publisher.tests.factories import (CourseFactory, CourseRunFactory, CourseRunStateFactory, from course_discovery.apps.publisher.tests.factories import (CourseFactory, CourseRunFactory, CourseRunStateFactory,
CourseStateFactory, CourseUserRoleFactory, CourseStateFactory, CourseUserRoleFactory,
OrganizationExtensionFactory, SeatFactory) OrganizationExtensionFactory, SeatFactory)
...@@ -237,12 +239,20 @@ class CourseRunStateSerializerTests(TestCase): ...@@ -237,12 +239,20 @@ class CourseRunStateSerializerTests(TestCase):
self.course_run.save() self.course_run.save()
self.course_run.staff.add(PersonFactory()) self.course_run.staff.add(PersonFactory())
toggle_switch('enable_publisher_email_notifications', True)
CourseUserRoleFactory(
course=self.course_run.course, role=PublisherUserRole.CourseTeam, user=self.user
)
CourseUserRoleFactory(
course=self.course_run.course, role=PublisherUserRole.ProjectCoordinator, user=UserFactory()
)
def test_update(self): def test_update(self):
""" """
Verify that we can update course-run workflow state name and preview_accepted with serializer. Verify that we can update course-run workflow state name and preview_accepted with serializer.
""" """
CourseUserRoleFactory( CourseUserRoleFactory(
course=self.course_run.course, role=PublisherUserRole.CourseTeam, user=self.user course=self.course_run.course, role=PublisherUserRole.Publisher, user=UserFactory()
) )
self.assertNotEqual(self.run_state, CourseRunStateChoices.Review) self.assertNotEqual(self.run_state, CourseRunStateChoices.Review)
...@@ -255,7 +265,12 @@ class CourseRunStateSerializerTests(TestCase): ...@@ -255,7 +265,12 @@ class CourseRunStateSerializerTests(TestCase):
self.assertFalse(self.run_state.preview_accepted) self.assertFalse(self.run_state.preview_accepted)
serializer.update(self.run_state, {'preview_accepted': True}) serializer.update(self.run_state, {'preview_accepted': True})
self.assertTrue(self.run_state.preview_accepted) self.assertTrue(CourseRun.objects.get(id=self.course_run.id).preview_url)
self.assertEqual(len(mail.outbox), 1)
subject = 'Preview for {run_name}'.format(
run_name=self.course_run.get_pacing_type_display()
)
self.assertIn(subject, str(mail.outbox[0].subject))
def test_update_with_error(self): def test_update_with_error(self):
""" """
...@@ -266,3 +281,21 @@ class CourseRunStateSerializerTests(TestCase): ...@@ -266,3 +281,21 @@ class CourseRunStateSerializerTests(TestCase):
with self.assertRaises(ValidationError): with self.assertRaises(ValidationError):
serializer.update(self.run_state, data) serializer.update(self.run_state, data)
self.assertEqual(len(mail.outbox), 1)
def test_update_with_transaction_roll_back(self):
"""
Verify that transaction roll back all db changes.
"""
self.assertNotEqual(self.run_state, CourseRunStateChoices.Review)
serializer = self.serializer_class(self.run_state, context={'request': self.request})
data = {'name': CourseRunStateChoices.Review}
with self.assertRaises(Exception):
serializer.update(self.run_state, data)
self.assertEqual(self.run_state.name, CourseRunStateChoices.Review)
self.assertFalse(self.run_state.preview_accepted)
serializer.update(self.run_state, {'preview_accepted': True})
self.assertFalse(CourseRun.objects.get(id=self.course_run.id).preview_url)
self.assertEqual(len(mail.outbox), 0)
...@@ -177,7 +177,6 @@ class OrganizationGroupUserViewTests(TestCase): ...@@ -177,7 +177,6 @@ class OrganizationGroupUserViewTests(TestCase):
path=self._get_organization_group_user_url(self.organization.id), content_type=JSON_CONTENT_TYPE path=self._get_organization_group_user_url(self.organization.id), content_type=JSON_CONTENT_TYPE
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
expected_results = [ expected_results = [
{ {
"id": self.org_user1.id, "id": self.org_user1.id,
...@@ -189,7 +188,8 @@ class OrganizationGroupUserViewTests(TestCase): ...@@ -189,7 +188,8 @@ class OrganizationGroupUserViewTests(TestCase):
} }
] ]
self.assertListEqual(json.loads(response.content.decode("utf-8"))["results"], expected_results) self.assertIn(expected_results[0], json.loads(response.content.decode("utf-8"))["results"])
self.assertIn(expected_results[1], json.loads(response.content.decode("utf-8"))["results"])
def test_get_organization_not_found(self): def test_get_organization_not_found(self):
""" Verify that view returns status=404 if organization is not found """ Verify that view returns status=404 if organization is not found
......
...@@ -324,7 +324,11 @@ def send_email_preview_accepted(course_run): ...@@ -324,7 +324,11 @@ def send_email_preview_accepted(course_run):
email_msg.attach_alternative(html_content, 'text/html') email_msg.attach_alternative(html_content, 'text/html')
email_msg.send() email_msg.send()
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
logger.exception('Failed to send email notifications for preview approved of course-run %s', course_run.id) message = 'Failed to send email notifications for preview approved of course-run [{id}].'.format(
id=course_run.id
)
logger.exception(message)
raise Exception(message)
def send_email_preview_page_is_available(course_run): def send_email_preview_page_is_available(course_run):
......
...@@ -618,7 +618,7 @@ class CourseRunState(TimeStampedModel, ChangedByMixin): ...@@ -618,7 +618,7 @@ class CourseRunState(TimeStampedModel, ChangedByMixin):
self.save() self.save()
def change_role(self, role): def change_owner_role(self, role):
self.owner_role = role self.owner_role = role
self.owner_role_modified = timezone.now() self.owner_role_modified = timezone.now()
self.save() self.save()
...@@ -627,6 +627,10 @@ class CourseRunState(TimeStampedModel, ChangedByMixin): ...@@ -627,6 +627,10 @@ class CourseRunState(TimeStampedModel, ChangedByMixin):
def is_preview_accepted(self): def is_preview_accepted(self):
return self.preview_accepted return self.preview_accepted
@property
def is_approved(self):
return self.name == CourseRunStateChoices.Approved
class PublisherUser(User): class PublisherUser(User):
""" Publisher User Proxy Model. """ """ Publisher User Proxy Model. """
......
...@@ -404,18 +404,21 @@ class CourseRunPreviewEmailTests(TestCase): ...@@ -404,18 +404,21 @@ class CourseRunPreviewEmailTests(TestCase):
def test_preview_accepted_email_with_error(self): def test_preview_accepted_email_with_error(self):
""" Verify that email failure log error message.""" """ Verify that email failure log error message."""
message = 'Failed to send email notifications for preview approved of course-run [{}]'.format(
self.run_state.course_run.id
)
with mock.patch('django.core.mail.message.EmailMessage.send', side_effect=TypeError): with mock.patch('django.core.mail.message.EmailMessage.send', side_effect=TypeError):
with LogCapture(emails.logger.name) as l: with self.assertRaises(Exception) as ex:
emails.send_email_preview_accepted(self.run_state.course_run) self.assertEqual(str(ex.exception), message)
l.check( with LogCapture(emails.logger.name) as l:
( emails.send_email_preview_accepted(self.run_state.course_run)
emails.logger.name, l.check(
'ERROR', (
'Failed to send email notifications for preview approved of course-run {}'.format( emails.logger.name,
self.run_state.course_run.id 'ERROR',
message
) )
) )
)
def test_preview_available_email(self): def test_preview_available_email(self):
""" """
......
...@@ -630,3 +630,23 @@ class CourseRunStateTests(TestCase): ...@@ -630,3 +630,23 @@ class CourseRunStateTests(TestCase):
self.course_run_state.preview_accepted = True self.course_run_state.preview_accepted = True
self.course_run_state.save() self.course_run_state.save()
self.assertTrue(self.course_run_state.is_preview_accepted) self.assertTrue(self.course_run_state.is_preview_accepted)
@ddt.data(
PublisherUserRole.Publisher,
PublisherUserRole.CourseTeam,
)
def test_change_owner_role(self, role):
"""
Verify that method change_owner_role updates the role.
"""
self.course_run_state.change_owner_role(role)
self.assertEqual(self.course_run_state.owner_role, role)
def test_is_approved(self):
"""
Verify that method return is_approved status.
"""
self.assertFalse(self.course_run_state.is_approved)
self.course_run_state.name = CourseRunStateChoices.Approved
self.course_run_state.save()
self.assertTrue(self.course_run_state.is_approved)
...@@ -32,6 +32,7 @@ from course_discovery.apps.publisher.utils import is_email_notification_enabled ...@@ -32,6 +32,7 @@ from course_discovery.apps.publisher.utils import is_email_notification_enabled
from course_discovery.apps.publisher.views import logger as publisher_views_logger from course_discovery.apps.publisher.views import logger as publisher_views_logger
from course_discovery.apps.publisher.views import CourseRunDetailView, get_course_role_widgets_data from course_discovery.apps.publisher.views import CourseRunDetailView, get_course_role_widgets_data
from course_discovery.apps.publisher.wrappers import CourseRunWrapper from course_discovery.apps.publisher.wrappers import CourseRunWrapper
from course_discovery.apps.publisher_comments.models import CommentTypeChoices
from course_discovery.apps.publisher_comments.tests.factories import CommentFactory from course_discovery.apps.publisher_comments.tests.factories import CommentFactory
IMAGE_TOO_SMALL = 'The image you uploaded is too small. The required minimum resolution is: 2120x1192 px.' IMAGE_TOO_SMALL = 'The image you uploaded is too small. The required minimum resolution is: 2120x1192 px.'
...@@ -864,6 +865,15 @@ class CourseRunDetailTests(TestCase): ...@@ -864,6 +865,15 @@ class CourseRunDetailTests(TestCase):
self.assertContains(response, comment.comment) self.assertContains(response, comment.comment)
self._assert_breadcrumbs(response, self.course_run) self._assert_breadcrumbs(response, self.course_run)
# test decline comment appearing on detail page also.
decline_comment = CommentFactory(
content_object=self.course_run,
user=self.user, site=site, comment_type=CommentTypeChoices.Decline_Preview
)
response = self.client.get(self.page_url)
self.assertContains(response, decline_comment.comment)
self.assertContains(response, '<b>Preview Decline:</b>')
def test_get_course_return_none(self): def test_get_course_return_none(self):
""" Verify that `PublisherPermissionMixin.get_course` return none """ Verify that `PublisherPermissionMixin.get_course` return none
if `publisher_object` doesn't have `course` attr. if `publisher_object` doesn't have `course` attr.
...@@ -1183,6 +1193,8 @@ class CourseRunDetailTests(TestCase): ...@@ -1183,6 +1193,8 @@ class CourseRunDetailTests(TestCase):
self.assertContains(response, 'COURSE PREVIEW') self.assertContains(response, 'COURSE PREVIEW')
self.assertContains(response, '<button class="btn btn-neutral btn-preview btn-preview-accept" type="button">') self.assertContains(response, '<button class="btn btn-neutral btn-preview btn-preview-accept" type="button">')
self.assertContains(response, '<button class="btn btn-neutral btn-preview btn-preview-decline" type="button">') self.assertContains(response, '<button class="btn btn-neutral btn-preview btn-preview-decline" type="button">')
self.assertContains(response, 'Reason for declining preview:')
self.assertContains(response, '<input type="button" value="Submit" class="btn btn-neutral btn-add-comment" />')
self.course_run_state.preview_accepted = True self.course_run_state.preview_accepted = True
self.course_run_state.owner_role = PublisherUserRole.Publisher self.course_run_state.owner_role = PublisherUserRole.Publisher
......
...@@ -7,6 +7,7 @@ from django.template.loader import get_template ...@@ -7,6 +7,7 @@ from django.template.loader import get_template
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from course_discovery.apps.publisher.models import CourseRun from course_discovery.apps.publisher.models import CourseRun
from course_discovery.apps.publisher.utils import is_email_notification_enabled
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -76,3 +77,52 @@ def send_email_for_comment(comment, created=False): ...@@ -76,3 +77,52 @@ def send_email_for_comment(comment, created=False):
email_msg.send() email_msg.send()
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
log.exception('Failed to send email notifications for comment %s', comment.id) log.exception('Failed to send email notifications for comment %s', comment.id)
def send_email_decline_preview(comment, course_run, preview_url):
""" Send the emails for a comment for decline preview.
Arguments:
comment (Comment): Comment object
course_run (CourseRun): course-run object
preview_url (url): preview_url
"""
try:
object_path = reverse('publisher:publisher_course_run_detail', args=[course_run.id])
# Translators: subject_desc will be Preview Decline for course run,
# 'title' will be the value of course title.
subject = _('Preview Decline for course run: {title}').format( # pylint: disable=no-member
title=course_run.course.title
)
recipient_user = course_run.course.publisher
if is_email_notification_enabled(recipient_user):
to_addresses = [recipient_user.email]
from_address = settings.PUBLISHER_FROM_EMAIL
context = {
'comment': comment,
'page_url': 'https://{host}{path}'.format(host=comment.site.domain.strip('/'), path=object_path),
'preview_url': preview_url,
'course_title': course_run.course.title,
}
txt_template = 'publisher/email/decline_preview.txt'
html_template = 'publisher/email/decline_preview.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
message = 'Failed to send email notifications for preview decline for course run [{id}].'.format(
id=course_run.id
)
log.exception(message)
raise Exception(message)
...@@ -2,12 +2,15 @@ from django import forms ...@@ -2,12 +2,15 @@ from django import forms
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django_comments.forms import CommentForm from django_comments.forms import CommentForm
from course_discovery.apps.publisher_comments.models import Comments from course_discovery.apps.publisher_comments.models import Comments, CommentTypeChoices
# pylint: disable=no-member # pylint: disable=no-member
class CommentsForm(CommentForm): class CommentsForm(CommentForm):
modified = forms.DateTimeField(required=False, widget=forms.HiddenInput) modified = forms.DateTimeField(required=False, widget=forms.HiddenInput)
comment_type = forms.ChoiceField(
required=False, choices=CommentTypeChoices.choices, initial=CommentTypeChoices.Default
)
def get_comment_model(self): def get_comment_model(self):
return Comments return Comments
...@@ -16,6 +19,7 @@ class CommentsForm(CommentForm): ...@@ -16,6 +19,7 @@ class CommentsForm(CommentForm):
# Use the data of the superclass, and add in the title field # Use the data of the superclass, and add in the title field
data = super(CommentsForm, self).get_comment_create_data() data = super(CommentsForm, self).get_comment_create_data()
data['modified'] = self.cleaned_data['modified'] data['modified'] = self.cleaned_data['modified']
data['comment_type'] = self.cleaned_data['comment_type']
return data return data
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
......
# -*- coding: utf-8 -*-
# Generated by Django 1.9.11 on 2017-02-24 09:38
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('publisher_comments', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='comments',
name='comment_type',
field=models.CharField(blank=True, choices=[('default', 'Default'), ('decline_preview', 'Decline Preview')], default='default', max_length=255, null=True),
),
]
import logging
import waffle import waffle
from django.db.models.signals import post_save from django.db import models, transaction
from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django_comments.models import CommentAbstractModel from django_comments.models import CommentAbstractModel
from django_extensions.db.fields import ModificationDateTimeField from django_extensions.db.fields import ModificationDateTimeField
from djchoices import ChoiceItem, DjangoChoices
from course_discovery.apps.publisher_comments.emails import send_email_for_comment from course_discovery.apps.publisher.choices import PublisherUserRole
from course_discovery.apps.publisher_comments.emails import send_email_decline_preview, send_email_for_comment
log = logging.getLogger(__name__)
class CommentTypeChoices(DjangoChoices):
Default = ChoiceItem('default', _('Default'))
Decline_Preview = ChoiceItem('decline_preview', _('Decline Preview'))
class Comments(CommentAbstractModel): class Comments(CommentAbstractModel):
DEFAULT = 'default'
DECLINE_PREVIEW = 'decline_preview'
modified = ModificationDateTimeField(_('modified')) modified = ModificationDateTimeField(_('modified'))
comment_type = models.CharField(
max_length=255, null=True, blank=True, choices=CommentTypeChoices.choices, default=CommentTypeChoices.Default
)
def save(self, *args, **kwargs):
if self.comment_type == CommentTypeChoices.Decline_Preview:
try:
mark_preview_url_as_decline(self)
except Exception: # pylint: disable=broad-except
# in case of exception don't save the comment
return
else:
if waffle.switch_is_active('enable_publisher_email_notifications'):
created = False if self.id else True
send_email_for_comment(self, created)
super(Comments, self).save(*args, **kwargs)
@transaction.atomic
def mark_preview_url_as_decline(instance):
course_run = instance.content_type.get_object_for_this_type(pk=instance.object_pk)
# remove the preview url
preview_url = course_run.preview_url
course_run.preview_url = None
course_run.save()
# change the owner role
course_run.course_run_state.change_owner_role(PublisherUserRole.Publisher)
@receiver(post_save, sender=Comments) # send email for decline preview
def send_email(sender, instance, **kwargs): # pylint: disable=unused-argument
""" Send email on new comment. """
if waffle.switch_is_active('enable_publisher_email_notifications'): if waffle.switch_is_active('enable_publisher_email_notifications'):
send_email_for_comment(instance, kwargs.get('created', False)) send_email_decline_preview(instance, course_run, preview_url)
...@@ -5,13 +5,16 @@ from django.contrib.sites.models import Site ...@@ -5,13 +5,16 @@ from django.contrib.sites.models import Site
from django.core import mail from django.core import mail
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.test import TestCase from django.test import TestCase
from testfixtures import LogCapture
from course_discovery.apps.core.tests.factories import UserFactory from course_discovery.apps.core.tests.factories import UserFactory
from course_discovery.apps.course_metadata.tests import toggle_switch from course_discovery.apps.course_metadata.tests import toggle_switch
from course_discovery.apps.publisher.choices import PublisherUserRole from course_discovery.apps.publisher.choices import PublisherUserRole
from course_discovery.apps.publisher.models import CourseUserRole from course_discovery.apps.publisher.models import CourseRun, CourseUserRole
from course_discovery.apps.publisher.tests import factories from course_discovery.apps.publisher.tests import factories
from course_discovery.apps.publisher.tests.factories import UserAttributeFactory from course_discovery.apps.publisher.tests.factories import UserAttributeFactory
from course_discovery.apps.publisher_comments.emails import log as comments_email_logger
from course_discovery.apps.publisher_comments.models import CommentTypeChoices
from course_discovery.apps.publisher_comments.tests.factories import CommentFactory from course_discovery.apps.publisher_comments.tests.factories import CommentFactory
...@@ -55,6 +58,7 @@ class CommentsEmailTests(TestCase): ...@@ -55,6 +58,7 @@ class CommentsEmailTests(TestCase):
UserAttributeFactory(user=self.user, enable_email_notification=True) UserAttributeFactory(user=self.user, enable_email_notification=True)
UserAttributeFactory(user=self.user_3, enable_email_notification=False) UserAttributeFactory(user=self.user_3, enable_email_notification=False)
toggle_switch('enable_publisher_email_notifications', True) toggle_switch('enable_publisher_email_notifications', True)
self.url = 'http://www.test.com'
def test_course_comment_email(self): def test_course_comment_email(self):
""" Verify that after adding a comment against a course emails send """ Verify that after adding a comment against a course emails send
...@@ -137,6 +141,7 @@ class CommentsEmailTests(TestCase): ...@@ -137,6 +141,7 @@ class CommentsEmailTests(TestCase):
self.assertIn(comment.comment, body) self.assertIn(comment.comment, body)
self.assertIn(page_url, body) self.assertIn(page_url, body)
self.assertIn('The edX team', body) self.assertIn('The edX team', body)
self.assertEqual(comment.comment_type, CommentTypeChoices.Default)
def test_email_with_roles(self): def test_email_with_roles(self):
""" Verify that emails send to the users against course-user-roles also.""" """ Verify that emails send to the users against course-user-roles also."""
...@@ -184,6 +189,9 @@ class CommentsEmailTests(TestCase): ...@@ -184,6 +189,9 @@ class CommentsEmailTests(TestCase):
""" Verify that after editing a comment against a course emails send """ Verify that after editing a comment against a course emails send
to multiple users. to multiple users.
""" """
factories.CourseUserRoleFactory(
course=self.course, role=PublisherUserRole.Publisher, user=self.user
)
comment = self.create_comment(content_object=self.course_run) comment = self.create_comment(content_object=self.course_run)
comment.comment = 'Update the comment' comment.comment = 'Update the comment'
comment.save() # pylint: disable=no-member comment.save() # pylint: disable=no-member
...@@ -196,8 +204,56 @@ class CommentsEmailTests(TestCase): ...@@ -196,8 +204,56 @@ class CommentsEmailTests(TestCase):
self.assertEqual(str(mail.outbox[1].subject), subject) self.assertEqual(str(mail.outbox[1].subject), subject)
self.assertIn(comment.comment, str(mail.outbox[1].body.strip()), 'Update the comment') self.assertIn(comment.comment, str(mail.outbox[1].body.strip()), 'Update the comment')
def create_comment(self, content_object): def test_decline_preview_email(self):
""" Verify that adding a comment in decline preview url send an email."""
user = UserFactory()
factories.CourseUserRoleFactory(
course=self.course, role=PublisherUserRole.Publisher, user=user
)
comment = self._create_decline_comment()
subject = 'Preview Decline for course run: {title}'.format(title=self.course.title)
self.assertEqual([user.email], mail.outbox[0].to)
self.assertEqual(str(mail.outbox[0].subject), subject)
body = 'Preview link {url} for the {title}: has been declined'.format(
url=self.url,
title=self.course.title
)
self.assertIn(body, str(mail.outbox[0].body.strip()))
self.assertEqual(comment.comment_type, CommentTypeChoices.Decline_Preview)
self.assertFalse(CourseRun.objects.get(id=self.course_run.id).preview_url)
def test_decline_preview_comment_with_role_back(self):
""" Verify that in case of any error transaction will roll back all changes."""
with LogCapture(comments_email_logger.name) as log_capture:
self._create_decline_comment()
message = 'Failed to send email notifications for preview decline for course run [{}].'.format(
self.course_run.id
)
log_capture.check((comments_email_logger.name, 'ERROR', message))
self.assertEqual(len(mail.outbox), 0)
self.assertTrue(CourseRun.objects.get(id=self.course_run.id).preview_url)
def test_decline_preview_comment_with_disable_email(self):
""" Verify that no email will be sent if publisher user has disabled the email."""
user = UserFactory()
factories.CourseUserRoleFactory(
course=self.course, role=PublisherUserRole.Publisher, user=user
)
factories.UserAttributeFactory(user=user, enable_email_notification=False)
self._create_decline_comment()
self.assertEqual(len(mail.outbox), 0)
def create_comment(self, content_object, comment_type=CommentTypeChoices.Default):
""" DRY method to create the comment for a given content type.""" """ DRY method to create the comment for a given content type."""
return CommentFactory( return CommentFactory(
content_object=content_object, user=self.user, site=self.site content_object=content_object, user=self.user, site=self.site,
comment_type=comment_type
) )
def _create_decline_comment(self):
self.course_run.preview_url = self.url
self.course_run.save()
factories.CourseRunStateFactory(course_run=self.course_run, owner_role=PublisherUserRole.CourseTeam)
return self.create_comment(content_object=self.course_run, comment_type=CommentTypeChoices.Decline_Preview)
...@@ -7,14 +7,14 @@ msgid "" ...@@ -7,14 +7,14 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-24 13:09+0500\n" "POT-Creation-Date: 2017-02-24 18:16+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Language: \n"
#: apps/api/filters.py #: apps/api/filters.py
#, python-brace-format #, python-brace-format
...@@ -826,6 +826,21 @@ msgstr "" ...@@ -826,6 +826,21 @@ msgstr ""
msgid "{subject_desc} in Course: {title}" msgid "{subject_desc} in Course: {title}"
msgstr "" msgstr ""
#. Translators: subject_desc will be Preview Decline for course run,
#. 'title' will be the value of course title.
#: apps/publisher_comments/emails.py
#, python-brace-format
msgid "Preview Decline for course run: {title}"
msgstr ""
#: apps/publisher_comments/models.py
msgid "Default"
msgstr ""
#: apps/publisher_comments/models.py
msgid "Decline Preview"
msgstr ""
#: apps/publisher_comments/models.py #: apps/publisher_comments/models.py
msgid "modified" msgid "modified"
msgstr "" msgstr ""
...@@ -834,6 +849,15 @@ msgstr "" ...@@ -834,6 +849,15 @@ msgstr ""
msgid "Comment:" msgid "Comment:"
msgstr "" msgstr ""
#: templates/comments/add_auth_comments.html
#: templates/publisher/course_run_detail/_widgets.html
msgid "Submit"
msgstr ""
#: templates/comments/add_auth_comments.html
msgid "Add comment"
msgstr ""
#: templates/comments/comments_list.html #: templates/comments/comments_list.html
msgid "Submitted by" msgid "Submitted by"
msgstr "" msgstr ""
...@@ -2160,6 +2184,10 @@ msgstr "" ...@@ -2160,6 +2184,10 @@ msgstr ""
msgid "Not available" msgid "Not available"
msgstr "" msgstr ""
#: templates/publisher/course_run_detail/_widgets.html
msgid "Reason for declining preview"
msgstr ""
#: templates/publisher/courses.html #: templates/publisher/courses.html
msgid "Runs" msgid "Runs"
msgstr "" msgstr ""
...@@ -2268,6 +2296,8 @@ msgstr "" ...@@ -2268,6 +2296,8 @@ msgstr ""
#: templates/publisher/email/course_run/preview_accepted.txt #: templates/publisher/email/course_run/preview_accepted.txt
#: templates/publisher/email/course_run/preview_available.html #: templates/publisher/email/course_run/preview_available.html
#: templates/publisher/email/course_run/preview_available.txt #: templates/publisher/email/course_run/preview_available.txt
#: templates/publisher/email/decline_preview.html
#: templates/publisher/email/decline_preview.txt
msgid "The edX team" msgid "The edX team"
msgstr "" msgstr ""
...@@ -2284,10 +2314,12 @@ msgid "" ...@@ -2284,10 +2314,12 @@ msgid ""
msgstr "" msgstr ""
#: templates/publisher/email/comment.html #: templates/publisher/email/comment.html
#: templates/publisher/email/decline_preview.html
msgid "View comment" msgid "View comment"
msgstr "" msgstr ""
#: templates/publisher/email/comment.txt #: templates/publisher/email/comment.txt
#: templates/publisher/email/decline_preview.txt
msgid "View comment: " msgid "View comment: "
msgstr "" msgstr ""
...@@ -2465,6 +2497,21 @@ msgid "" ...@@ -2465,6 +2497,21 @@ msgid ""
"%(link_end)s to approve or decline the changes." "%(link_end)s to approve or decline the changes."
msgstr "" msgstr ""
#: templates/publisher/email/decline_preview.html
#, python-format
msgid ""
"Preview link %(preview_url)s for the "
"%(link_start)s%(page_url)s%(link_middle)s%(course_title)s%(link_end)s has "
"been declined."
msgstr ""
#: templates/publisher/email/decline_preview.txt
#, python-format
msgid ""
"Preview link %(preview_url)s for the %(course_title)s: %(course_url)s has "
"been declined."
msgstr ""
#. Translators: course_team_name is course team member name. #. Translators: course_team_name is course team member name.
#: templates/publisher/email/studio_instance_created.html #: templates/publisher/email/studio_instance_created.html
#: templates/publisher/email/studio_instance_created.txt #: templates/publisher/email/studio_instance_created.txt
......
...@@ -7,14 +7,14 @@ msgid "" ...@@ -7,14 +7,14 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-24 13:09+0500\n" "POT-Creation-Date: 2017-02-24 18:17+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Language: \n"
#: static/js/catalogs-change-form.js #: static/js/catalogs-change-form.js
msgid "Preview" msgid "Preview"
......
...@@ -7,14 +7,14 @@ msgid "" ...@@ -7,14 +7,14 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-24 13:09+0500\n" "POT-Creation-Date: 2017-02-24 18:16+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Language: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: apps/api/filters.py #: apps/api/filters.py
...@@ -979,6 +979,23 @@ msgstr "" ...@@ -979,6 +979,23 @@ msgstr ""
msgid "{subject_desc} in Course: {title}" msgid "{subject_desc} in Course: {title}"
msgstr "{subject_desc} ïn Çöürsé: {title} Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт#" msgstr "{subject_desc} ïn Çöürsé: {title} Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт#"
#. Translators: subject_desc will be Preview Decline for course run,
#. 'title' will be the value of course title.
#: apps/publisher_comments/emails.py
#, python-brace-format
msgid "Preview Decline for course run: {title}"
msgstr ""
"Prévïéw Déçlïné för çöürsé rün: {title} Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, "
"¢σηѕє¢тєт#"
#: apps/publisher_comments/models.py
msgid "Default"
msgstr "Défäült Ⱡ'σяєм ιρѕυм #"
#: apps/publisher_comments/models.py
msgid "Decline Preview"
msgstr "Déçlïné Prévïéw Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт α#"
#: apps/publisher_comments/models.py #: apps/publisher_comments/models.py
msgid "modified" msgid "modified"
msgstr "mödïfïéd Ⱡ'σяєм ιρѕυм ∂#" msgstr "mödïfïéd Ⱡ'σяєм ιρѕυм ∂#"
...@@ -987,6 +1004,15 @@ msgstr "mödïfïéd Ⱡ'σяєм ιρѕυм ∂#" ...@@ -987,6 +1004,15 @@ msgstr "mödïfïéd Ⱡ'σяєм ιρѕυм ∂#"
msgid "Comment:" msgid "Comment:"
msgstr "Çömmént: Ⱡ'σяєм ιρѕυм ∂#" msgstr "Çömmént: Ⱡ'σяєм ιρѕυм ∂#"
#: templates/comments/add_auth_comments.html
#: templates/publisher/course_run_detail/_widgets.html
msgid "Submit"
msgstr "Süßmït Ⱡ'σяєм ιρѕυ#"
#: templates/comments/add_auth_comments.html
msgid "Add comment"
msgstr "Àdd çömmént Ⱡ'σяєм ιρѕυм ∂σłσя #"
#: templates/comments/comments_list.html #: templates/comments/comments_list.html
msgid "Submitted by" msgid "Submitted by"
msgstr "Süßmïttéd ßý Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#" msgstr "Süßmïttéd ßý Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
...@@ -2539,6 +2565,10 @@ msgstr "Vïéw çöürsé prévïéw lïvé Ⱡ'σяєм ιρѕυм ∂σłσя ...@@ -2539,6 +2565,10 @@ msgstr "Vïéw çöürsé prévïéw lïvé Ⱡ'σяєм ιρѕυм ∂σłσя
msgid "Not available" msgid "Not available"
msgstr "Nöt äväïläßlé Ⱡ'σяєм ιρѕυм ∂σłσя ѕι#" msgstr "Nöt äväïläßlé Ⱡ'σяєм ιρѕυм ∂σłσя ѕι#"
#: templates/publisher/course_run_detail/_widgets.html
msgid "Reason for declining preview"
msgstr "Réäsön för déçlïnïng prévïéw Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢#"
#: templates/publisher/courses.html #: templates/publisher/courses.html
msgid "Runs" msgid "Runs"
msgstr "Rüns Ⱡ'σяєм ι#" msgstr "Rüns Ⱡ'σяєм ι#"
...@@ -2666,6 +2696,8 @@ msgstr "Vïéw Çöürsé Ⱡ'σяєм ιρѕυм ∂σłσя #" ...@@ -2666,6 +2696,8 @@ msgstr "Vïéw Çöürsé Ⱡ'σяєм ιρѕυм ∂σłσя #"
#: templates/publisher/email/course_run/preview_accepted.txt #: templates/publisher/email/course_run/preview_accepted.txt
#: templates/publisher/email/course_run/preview_available.html #: templates/publisher/email/course_run/preview_available.html
#: templates/publisher/email/course_run/preview_available.txt #: templates/publisher/email/course_run/preview_available.txt
#: templates/publisher/email/decline_preview.html
#: templates/publisher/email/decline_preview.txt
msgid "The edX team" msgid "The edX team"
msgstr "Thé édX téäm Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#" msgstr "Thé édX téäm Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
...@@ -2684,10 +2716,12 @@ msgstr "" ...@@ -2684,10 +2716,12 @@ msgstr ""
"%(title)s (%(number)s). Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α#" "%(title)s (%(number)s). Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α#"
#: templates/publisher/email/comment.html #: templates/publisher/email/comment.html
#: templates/publisher/email/decline_preview.html
msgid "View comment" msgid "View comment"
msgstr "Vïéw çömmént Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#" msgstr "Vïéw çömmént Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
#: templates/publisher/email/comment.txt #: templates/publisher/email/comment.txt
#: templates/publisher/email/decline_preview.txt
msgid "View comment: " msgid "View comment: "
msgstr "Vïéw çömmént: Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт#" msgstr "Vïéw çömmént: Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт#"
...@@ -2920,6 +2954,26 @@ msgstr "" ...@@ -2920,6 +2954,26 @@ msgstr ""
" ραяιαтυя. єχ¢єρтєυя ѕιηт σ¢¢αє¢αт ¢υρι∂αтαт ηση ρяσι∂єηт, ѕυηт ιη ¢υłρα qυι" " ραяιαтυя. єχ¢єρтєυя ѕιηт σ¢¢αє¢αт ¢υρι∂αтαт ηση ρяσι∂єηт, ѕυηт ιη ¢υłρα qυι"
" σƒƒι¢ια ∂єѕєяυηт мσłłιт αηιм ι∂ єѕт#" " σƒƒι¢ια ∂єѕєяυηт мσłłιт αηιм ι∂ єѕт#"
#: templates/publisher/email/decline_preview.html
#, python-format
msgid ""
"Preview link %(preview_url)s for the "
"%(link_start)s%(page_url)s%(link_middle)s%(course_title)s%(link_end)s has "
"been declined."
msgstr ""
"Prévïéw lïnk %(preview_url)s för thé "
"%(link_start)s%(page_url)s%(link_middle)s%(course_title)s%(link_end)s häs "
"ßéén déçlïnéd. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α#"
#: templates/publisher/email/decline_preview.txt
#, python-format
msgid ""
"Preview link %(preview_url)s for the %(course_title)s: %(course_url)s has "
"been declined."
msgstr ""
"Prévïéw lïnk %(preview_url)s för thé %(course_title)s: %(course_url)s häs "
"ßéén déçlïnéd. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α#"
#. Translators: course_team_name is course team member name. #. Translators: course_team_name is course team member name.
#: templates/publisher/email/studio_instance_created.html #: templates/publisher/email/studio_instance_created.html
#: templates/publisher/email/studio_instance_created.txt #: templates/publisher/email/studio_instance_created.txt
......
...@@ -7,14 +7,14 @@ msgid "" ...@@ -7,14 +7,14 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-02-24 13:09+0500\n" "POT-Creation-Date: 2017-02-24 18:17+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Language: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: static/js/catalogs-change-form.js #: static/js/catalogs-change-form.js
......
$(document).ready(function() { $(document).on('click', '.btn-add-comment', function (e) {
var box = $('#id_comment'); e.preventDefault();
$("#id_submit").click(function(event){
if( !box.val() ) { var frm_comment = $(this).closest("#frm_comment"),
box.addClass('has-error'); comment_box = frm_comment.find("#id_comment");
box.focus();
}
else{
$("#frm_comment").submit();
}
}); if (!comment_box.val()) {
comment_box.addClass('has-error');
comment_box.focus();
}
else {
$(frm_comment).submit();
}
}); });
$(document).on('click', '.comment-edit', function (e) { $(document).on('click', '.comment-edit', function (e) {
......
...@@ -365,3 +365,7 @@ $(document).on('click', '.btn-edit-preview-url', function (e) { ...@@ -365,3 +365,7 @@ $(document).on('click', '.btn-edit-preview-url', function (e) {
$previewUrl.html(html); $previewUrl.html(html);
$('#id-review-url').focus(); $('#id-review-url').focus();
}); });
$('.btn-preview-decline').click(function(e){
$('#decline-comment').toggle();
});
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
{% if user.is_authenticated and comment_object %} {% if user.is_authenticated and comment_object %}
<div> <div>
<h5 class="hd-5 emphasized">{% trans 'Comment:' %}</h5> <h5 class="hd-5 emphasized">{% if box_label %}{{ box_label }}:{% else %}{% trans 'Comment:' %}{% endif %}</h5>
<div> <div>
{% get_comment_form for comment_object as form %} {% get_comment_form for comment_object as form %}
<form id="frm_comment" action="{% comment_form_target %}" method="POST"> <form id="frm_comment" action="{% comment_form_target %}" method="POST">
...@@ -13,9 +13,14 @@ ...@@ -13,9 +13,14 @@
{{ form.object_pk }} {{ form.object_pk }}
{{ form.timestamp }} {{ form.timestamp }}
{{ form.security_hash }} {{ form.security_hash }}
<input type="hidden" name="comment_type" value="{{ comment_type }}"/>
<input type="hidden" name="next" value="{{ post_back_url }}"/> <input type="hidden" name="next" value="{{ post_back_url }}"/>
<div class="add-comment"> <div class="add-comment">
<input type="button" value="Add comment" id="id_submit" class="btn btn-brand btn-small btn-course-add" /> {% if btn_label %}
<input type="button" value="{% trans 'Submit' %}" class="btn btn-neutral btn-add-comment" />
{% else %}
<input type="button" value="{% trans 'Add comment' %}" class="btn btn-brand btn-small btn-course-add btn-add-comment" />
{% endif %}
</div> </div>
</form> </form>
</div> </div>
......
...@@ -6,9 +6,11 @@ ...@@ -6,9 +6,11 @@
<dl class="comments"> <dl class="comments">
{% for comment in comment_list reversed %} {% for comment in comment_list reversed %}
<dt class="align-right"><span class="datetime">{{ comment.modified|date:"F d, Y, H:i:s a" }}&nbsp;</span></dt> <dt class="align-right"><span class="datetime">{{ comment.modified|date:"F d, Y, H:i:s a" }}&nbsp;</span></dt>
<dd class="edit-comment"> {% ifequal comment.comment_type 'decline_preview' %}<b>Preview Decline:</b>
{{ comment.comment }} <dd class="edit-comment">{{ comment.comment }}</dd>
</dd> {% else %}
<dd class="edit-comment">{{ comment.comment }}</dd>
{% endifequal %}
<dt class="submitted-by"> <dt class="submitted-by">
<span> <span>
<em>{% trans 'Submitted by' %}&nbsp;{{ comment.name }}</em> <em>{% trans 'Submitted by' %}&nbsp;{{ comment.name }}</em>
......
...@@ -64,7 +64,7 @@ ...@@ -64,7 +64,7 @@
{% endwith %} {% endwith %}
</div> </div>
<div id="comments-widget" class="comment-container {% if not publisher_comment_widget_feature %}hidden{% endif %}"> <div id="comments-widget" class="comment-container {% if not publisher_comment_widget_feature %}hidden{% endif %}">
{% include 'comments/add_auth_comments.html' %} {% include 'comments/add_auth_comments.html' with comment_type='default' %}
{% include 'comments/comments_list.html' %} {% include 'comments/comments_list.html' %}
</div> </div>
</aside> </aside>
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
<div class="layout-1q3q layout-reversed"> <div class="layout-1q3q layout-reversed">
<div class="layout-col layout-col-a"> <div class="layout-col layout-col-a">
{% if object.preview_url %} {% if object.preview_url %}
{% if object.course.course_team_admin == request.user and not object.course_run_state.is_preview_accepted %} {% if object.preview_url and object.course.course_team_admin == request.user and object.course_run_state.is_approved and not object.course_run_state.is_preview_accepted %}
<button class="btn btn-neutral btn-preview btn-preview-decline" type="button"> <button class="btn btn-neutral btn-preview btn-preview-decline" type="button">
{% trans "Decline" %} {% trans "Decline" %}
</button> </button>
...@@ -58,7 +58,11 @@ ...@@ -58,7 +58,11 @@
</div> </div>
</div> </div>
<hr> <hr>
<div id="decline-comment" class="hidden clearfix">
{% trans 'Reason for declining preview' as decline_reason %}
{% trans 'Submit' as submit %}
{% include 'comments/add_auth_comments.html' with comment_type='decline_preview' box_label=decline_reason btn_label=submit %}
</div>
{% include 'publisher/course_run_detail/_preview_accept_popup.html' %} {% include 'publisher/course_run_detail/_preview_accept_popup.html' %}
</div> </div>
</div> </div>
{% extends "publisher/email/email_base.html" %}
{% load i18n %}
{% block body %}
<!-- Message Body -->
<p>
{% blocktrans trimmed with link_start='<a href="' link_middle='">' link_end='</a>' %}
Preview link {{ preview_url }} for the {{ link_start }}{{ page_url }}{{ link_middle }}{{ course_title }}{{ link_end }}
has been declined.
{% 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 trimmed %}
Preview link {{ preview_url }} for the {{ course_title }}: {{ course_url }} has been declined.
{% endblocktrans %}
{{ comment.comment }}
{% trans "View comment: " %}{{ page_url }}
{% trans "The edX team" %}
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