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):
lms_course_id = validated_data.get('lms_course_id')
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 preview_url:
......
"""Tests API Serializers."""
from django.core import mail
from django.test import RequestFactory, TestCase
from rest_framework.exceptions import ValidationError
from course_discovery.apps.core.tests.factories import UserFactory
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.ietf_language_tags.models import LanguageTag
from course_discovery.apps.publisher.api.serializers import (CourseRevisionSerializer, CourseRunSerializer,
CourseRunStateSerializer, CourseStateSerializer,
CourseUserRoleSerializer, GroupUserSerializer)
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,
CourseStateFactory, CourseUserRoleFactory,
OrganizationExtensionFactory, SeatFactory)
......@@ -237,12 +239,20 @@ class CourseRunStateSerializerTests(TestCase):
self.course_run.save()
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):
"""
Verify that we can update course-run workflow state name and preview_accepted with serializer.
"""
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)
......@@ -255,7 +265,12 @@ class CourseRunStateSerializerTests(TestCase):
self.assertFalse(self.run_state.preview_accepted)
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):
"""
......@@ -266,3 +281,21 @@ class CourseRunStateSerializerTests(TestCase):
with self.assertRaises(ValidationError):
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):
path=self._get_organization_group_user_url(self.organization.id), content_type=JSON_CONTENT_TYPE
)
self.assertEqual(response.status_code, 200)
expected_results = [
{
"id": self.org_user1.id,
......@@ -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):
""" Verify that view returns status=404 if organization is not found
......
......@@ -324,7 +324,11 @@ def send_email_preview_accepted(course_run):
email_msg.attach_alternative(html_content, 'text/html')
email_msg.send()
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):
......
......@@ -618,7 +618,7 @@ class CourseRunState(TimeStampedModel, ChangedByMixin):
self.save()
def change_role(self, role):
def change_owner_role(self, role):
self.owner_role = role
self.owner_role_modified = timezone.now()
self.save()
......@@ -627,6 +627,10 @@ class CourseRunState(TimeStampedModel, ChangedByMixin):
def is_preview_accepted(self):
return self.preview_accepted
@property
def is_approved(self):
return self.name == CourseRunStateChoices.Approved
class PublisherUser(User):
""" Publisher User Proxy Model. """
......
......@@ -404,18 +404,21 @@ class CourseRunPreviewEmailTests(TestCase):
def test_preview_accepted_email_with_error(self):
""" 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 LogCapture(emails.logger.name) as l:
emails.send_email_preview_accepted(self.run_state.course_run)
l.check(
(
emails.logger.name,
'ERROR',
'Failed to send email notifications for preview approved of course-run {}'.format(
self.run_state.course_run.id
with self.assertRaises(Exception) as ex:
self.assertEqual(str(ex.exception), message)
with LogCapture(emails.logger.name) as l:
emails.send_email_preview_accepted(self.run_state.course_run)
l.check(
(
emails.logger.name,
'ERROR',
message
)
)
)
def test_preview_available_email(self):
"""
......
......@@ -630,3 +630,23 @@ class CourseRunStateTests(TestCase):
self.course_run_state.preview_accepted = True
self.course_run_state.save()
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
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.wrappers import CourseRunWrapper
from course_discovery.apps.publisher_comments.models import CommentTypeChoices
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.'
......@@ -864,6 +865,15 @@ class CourseRunDetailTests(TestCase):
self.assertContains(response, comment.comment)
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):
""" Verify that `PublisherPermissionMixin.get_course` return none
if `publisher_object` doesn't have `course` attr.
......@@ -1183,6 +1193,8 @@ class CourseRunDetailTests(TestCase):
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-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.owner_role = PublisherUserRole.Publisher
......
......@@ -7,6 +7,7 @@ from django.template.loader import get_template
from django.utils.translation import ugettext_lazy as _
from course_discovery.apps.publisher.models import CourseRun
from course_discovery.apps.publisher.utils import is_email_notification_enabled
log = logging.getLogger(__name__)
......@@ -76,3 +77,52 @@ def send_email_for_comment(comment, created=False):
email_msg.send()
except Exception: # pylint: disable=broad-except
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
from django.contrib.contenttypes.models import ContentType
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
class CommentsForm(CommentForm):
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):
return Comments
......@@ -16,6 +19,7 @@ class CommentsForm(CommentForm):
# Use the data of the superclass, and add in the title field
data = super(CommentsForm, self).get_comment_create_data()
data['modified'] = self.cleaned_data['modified']
data['comment_type'] = self.cleaned_data['comment_type']
return data
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
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.db import models, transaction
from django.utils.translation import ugettext_lazy as _
from django_comments.models import CommentAbstractModel
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):
DEFAULT = 'default'
DECLINE_PREVIEW = 'decline_preview'
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)
def send_email(sender, instance, **kwargs): # pylint: disable=unused-argument
""" Send email on new comment. """
# send email for decline preview
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
from django.core import mail
from django.core.urlresolvers import reverse
from django.test import TestCase
from testfixtures import LogCapture
from course_discovery.apps.core.tests.factories import UserFactory
from course_discovery.apps.course_metadata.tests import toggle_switch
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.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
......@@ -55,6 +58,7 @@ class CommentsEmailTests(TestCase):
UserAttributeFactory(user=self.user, enable_email_notification=True)
UserAttributeFactory(user=self.user_3, enable_email_notification=False)
toggle_switch('enable_publisher_email_notifications', True)
self.url = 'http://www.test.com'
def test_course_comment_email(self):
""" Verify that after adding a comment against a course emails send
......@@ -137,6 +141,7 @@ class CommentsEmailTests(TestCase):
self.assertIn(comment.comment, body)
self.assertIn(page_url, body)
self.assertIn('The edX team', body)
self.assertEqual(comment.comment_type, CommentTypeChoices.Default)
def test_email_with_roles(self):
""" Verify that emails send to the users against course-user-roles also."""
......@@ -184,6 +189,9 @@ class CommentsEmailTests(TestCase):
""" Verify that after editing a comment against a course emails send
to multiple users.
"""
factories.CourseUserRoleFactory(
course=self.course, role=PublisherUserRole.Publisher, user=self.user
)
comment = self.create_comment(content_object=self.course_run)
comment.comment = 'Update the comment'
comment.save() # pylint: disable=no-member
......@@ -196,8 +204,56 @@ class CommentsEmailTests(TestCase):
self.assertEqual(str(mail.outbox[1].subject), subject)
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."""
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 ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: \n"
#: apps/api/filters.py
#, python-brace-format
......@@ -826,6 +826,21 @@ msgstr ""
msgid "{subject_desc} in Course: {title}"
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
msgid "modified"
msgstr ""
......@@ -834,6 +849,15 @@ msgstr ""
msgid "Comment:"
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
msgid "Submitted by"
msgstr ""
......@@ -2160,6 +2184,10 @@ msgstr ""
msgid "Not available"
msgstr ""
#: templates/publisher/course_run_detail/_widgets.html
msgid "Reason for declining preview"
msgstr ""
#: templates/publisher/courses.html
msgid "Runs"
msgstr ""
......@@ -2268,6 +2296,8 @@ msgstr ""
#: templates/publisher/email/course_run/preview_accepted.txt
#: templates/publisher/email/course_run/preview_available.html
#: templates/publisher/email/course_run/preview_available.txt
#: templates/publisher/email/decline_preview.html
#: templates/publisher/email/decline_preview.txt
msgid "The edX team"
msgstr ""
......@@ -2284,10 +2314,12 @@ msgid ""
msgstr ""
#: templates/publisher/email/comment.html
#: templates/publisher/email/decline_preview.html
msgid "View comment"
msgstr ""
#: templates/publisher/email/comment.txt
#: templates/publisher/email/decline_preview.txt
msgid "View comment: "
msgstr ""
......@@ -2465,6 +2497,21 @@ msgid ""
"%(link_end)s to approve or decline the changes."
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.
#: templates/publisher/email/studio_instance_created.html
#: templates/publisher/email/studio_instance_created.txt
......
......@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: \n"
#: static/js/catalogs-change-form.js
msgid "Preview"
......
......@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: apps/api/filters.py
......@@ -979,6 +979,23 @@ msgstr ""
msgid "{subject_desc} in Course: {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
msgid "modified"
msgstr "mödïfïéd Ⱡ'σяєм ιρѕυм ∂#"
......@@ -987,6 +1004,15 @@ msgstr "mödïfïéd Ⱡ'σяєм ιρѕυм ∂#"
msgid "Comment:"
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
msgid "Submitted by"
msgstr "Süßmïttéd ßý Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
......@@ -2539,6 +2565,10 @@ msgstr "Vïéw çöürsé prévïéw lïvé Ⱡ'σяєм ιρѕυм ∂σłσя
msgid "Not available"
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
msgid "Runs"
msgstr "Rüns Ⱡ'σяєм ι#"
......@@ -2666,6 +2696,8 @@ msgstr "Vïéw Çöürsé Ⱡ'σяєм ιρѕυм ∂σłσя #"
#: templates/publisher/email/course_run/preview_accepted.txt
#: templates/publisher/email/course_run/preview_available.html
#: templates/publisher/email/course_run/preview_available.txt
#: templates/publisher/email/decline_preview.html
#: templates/publisher/email/decline_preview.txt
msgid "The edX team"
msgstr "Thé édX téäm Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
......@@ -2684,10 +2716,12 @@ msgstr ""
"%(title)s (%(number)s). Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α#"
#: templates/publisher/email/comment.html
#: templates/publisher/email/decline_preview.html
msgid "View comment"
msgstr "Vïéw çömmént Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
#: templates/publisher/email/comment.txt
#: templates/publisher/email/decline_preview.txt
msgid "View comment: "
msgstr "Vïéw çömmént: Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт#"
......@@ -2920,6 +2954,26 @@ msgstr ""
" ραяιαтυя. єχ¢єρтєυя ѕιηт σ¢¢αє¢αт ¢υρι∂αтαт ηση ρяσι∂єηт, ѕυηт ιη ¢υłρα 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.
#: templates/publisher/email/studio_instance_created.html
#: templates/publisher/email/studio_instance_created.txt
......
......@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: \n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: static/js/catalogs-change-form.js
......
$(document).ready(function() {
var box = $('#id_comment');
$("#id_submit").click(function(event){
if( !box.val() ) {
box.addClass('has-error');
box.focus();
}
else{
$("#frm_comment").submit();
}
$(document).on('click', '.btn-add-comment', function (e) {
e.preventDefault();
var frm_comment = $(this).closest("#frm_comment"),
comment_box = frm_comment.find("#id_comment");
});
if (!comment_box.val()) {
comment_box.addClass('has-error');
comment_box.focus();
}
else {
$(frm_comment).submit();
}
});
$(document).on('click', '.comment-edit', function (e) {
......
......@@ -365,3 +365,7 @@ $(document).on('click', '.btn-edit-preview-url', function (e) {
$previewUrl.html(html);
$('#id-review-url').focus();
});
$('.btn-preview-decline').click(function(e){
$('#decline-comment').toggle();
});
......@@ -3,7 +3,7 @@
{% if user.is_authenticated and comment_object %}
<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>
{% get_comment_form for comment_object as form %}
<form id="frm_comment" action="{% comment_form_target %}" method="POST">
......@@ -13,9 +13,14 @@
{{ form.object_pk }}
{{ form.timestamp }}
{{ form.security_hash }}
<input type="hidden" name="comment_type" value="{{ comment_type }}"/>
<input type="hidden" name="next" value="{{ post_back_url }}"/>
<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>
</form>
</div>
......
......@@ -6,9 +6,11 @@
<dl class="comments">
{% 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>
<dd class="edit-comment">
{{ comment.comment }}
</dd>
{% ifequal comment.comment_type 'decline_preview' %}<b>Preview Decline:</b>
<dd class="edit-comment">{{ comment.comment }}</dd>
{% else %}
<dd class="edit-comment">{{ comment.comment }}</dd>
{% endifequal %}
<dt class="submitted-by">
<span>
<em>{% trans 'Submitted by' %}&nbsp;{{ comment.name }}</em>
......
......@@ -64,7 +64,7 @@
{% endwith %}
</div>
<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' %}
</div>
</aside>
......
......@@ -15,7 +15,7 @@
<div class="layout-1q3q layout-reversed">
<div class="layout-col layout-col-a">
{% 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">
{% trans "Decline" %}
</button>
......@@ -58,7 +58,11 @@
</div>
</div>
<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' %}
</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