Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
C
course-discovery
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
course-discovery
Commits
de83e87f
Commit
de83e87f
authored
Feb 14, 2017
by
Waheed Ahmed
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added mark as reviewed functionality for parent course.
ECOM-6347
parent
a297c8e0
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
410 additions
and
162 deletions
+410
-162
course_discovery/apps/publisher/api/tests/test_views.py
+36
-4
course_discovery/apps/publisher/emails.py
+42
-10
course_discovery/apps/publisher/models.py
+13
-1
course_discovery/apps/publisher/tests/test_emails.py
+48
-0
course_discovery/apps/publisher/tests/test_views.py
+51
-10
course_discovery/apps/publisher/views.py
+33
-13
course_discovery/conf/locale/en/LC_MESSAGES/django.po
+84
-58
course_discovery/conf/locale/eo/LC_MESSAGES/django.po
+0
-0
course_discovery/static/sass/publisher/publisher.scss
+1
-0
course_discovery/templates/publisher/_approval_widget.html
+46
-13
course_discovery/templates/publisher/course_detail.html
+1
-0
course_discovery/templates/publisher/course_detail/_widgets.html
+1
-1
course_discovery/templates/publisher/course_run_detail.html
+1
-1
course_discovery/templates/publisher/course_run_detail/_approval_widget.html
+0
-51
course_discovery/templates/publisher/course_run_detail/_widgets.html
+13
-0
course_discovery/templates/publisher/email/course/mark_as_reviewed.html
+25
-0
course_discovery/templates/publisher/email/course/mark_as_reviewed.txt
+15
-0
course_discovery/templates/publisher/email/course/send_for_review.html
+0
-0
course_discovery/templates/publisher/email/course/send_for_review.txt
+0
-0
No files found.
course_discovery/apps/publisher/api/tests/test_views.py
View file @
de83e87f
...
...
@@ -425,7 +425,8 @@ class ChangeCourseStateViewTests(TestCase):
self
.
assertEqual
(
self
.
course_state
.
name
,
CourseStateChoices
.
Review
)
self
.
assertEqual
(
self
.
course_state
.
owner_role
,
PublisherUserRole
.
CourseTeam
)
self
.
_assert_email_sent
(
course_team_user
)
subject
=
'Changes to {title} are ready for review'
.
format
(
title
=
self
.
course
.
title
)
self
.
_assert_email_sent
(
course_team_user
,
subject
)
def
test_change_course_state_with_course_team
(
self
):
"""
...
...
@@ -461,10 +462,10 @@ class ChangeCourseStateViewTests(TestCase):
self
.
assertEqual
(
self
.
course_state
.
owner_role
,
PublisherUserRole
.
MarketingReviewer
)
self
.
assertGreater
(
self
.
course_state
.
owner_role_modified
,
old_owner_role_modified
)
self
.
_assert_email_sent
(
marketing_user
)
def
_assert_email_sent
(
self
,
user
):
subject
=
'Changes to {title} are ready for review'
.
format
(
title
=
self
.
course
.
title
)
self
.
_assert_email_sent
(
marketing_user
,
subject
)
def
_assert_email_sent
(
self
,
user
,
subject
):
self
.
assertEqual
(
len
(
mail
.
outbox
),
1
)
self
.
assertEqual
([
user
.
email
],
mail
.
outbox
[
0
]
.
to
)
self
.
assertEqual
(
str
(
mail
.
outbox
[
0
]
.
subject
),
subject
)
...
...
@@ -494,6 +495,37 @@ class ChangeCourseStateViewTests(TestCase):
self
.
assertEqual
(
response
.
data
,
expected
)
def
test_mark_as_reviewed
(
self
):
"""
Verify that user can mark course as reviewed.
"""
self
.
course_state
.
name
=
CourseStateChoices
.
Review
self
.
course_state
.
save
()
factories
.
CourseUserRoleFactory
(
course
=
self
.
course
,
role
=
PublisherUserRole
.
MarketingReviewer
,
user
=
self
.
user
)
course_team_user
=
UserFactory
()
factories
.
CourseUserRoleFactory
(
course
=
self
.
course
,
role
=
PublisherUserRole
.
CourseTeam
,
user
=
course_team_user
)
response
=
self
.
client
.
patch
(
self
.
change_state_url
,
data
=
json
.
dumps
({
'name'
:
CourseStateChoices
.
Approved
}),
content_type
=
JSON_CONTENT_TYPE
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
course_state
=
CourseState
.
objects
.
get
(
course
=
self
.
course
)
self
.
assertEqual
(
self
.
course_state
.
name
,
CourseStateChoices
.
Approved
)
subject
=
'Changes to {title} has been approved'
.
format
(
title
=
self
.
course
.
title
)
self
.
_assert_email_sent
(
course_team_user
,
subject
)
class
ChangeCourseRunStateViewTests
(
TestCase
):
...
...
course_discovery/apps/publisher/emails.py
View file @
de83e87f
...
...
@@ -8,6 +8,7 @@ from django.template.loader import get_template
from
django.utils.translation
import
ugettext_lazy
as
_
from
course_discovery.apps.publisher.choices
import
PublisherUserRole
from
course_discovery.apps.publisher.utils
import
is_email_notification_enabled
logger
=
logging
.
getLogger
(
__name__
)
...
...
@@ -155,16 +156,50 @@ def send_email_for_send_for_review(course, user):
course (Object): Course object
user (Object): User object
"""
txt_template
=
'publisher/email/course/send_for_review.txt'
html_template
=
'publisher/email/course/send_for_review.html'
subject
=
_
(
'Changes to {title} are ready for review'
)
.
format
(
title
=
course
.
title
)
# pylint: disable=no-member
try
:
txt_template
=
'publisher/email/send_for_review.txt'
html_template
=
'publisher/email/send_for_review.html'
send_course_workflow_email
(
course
,
user
,
subject
,
txt_template
,
html_template
)
except
Exception
:
# pylint: disable=broad-except
logger
.
exception
(
'Failed to send email notifications send for review of course
%
s'
,
course
.
id
)
recipient_user
=
course
.
marketing_reviewer
user_role
=
course
.
course_user_roles
.
get
(
user
=
user
)
if
user_role
.
role
==
PublisherUserRole
.
MarketingReviewer
:
recipient_user
=
course
.
course_team_admin
def
send_email_for_mark_as_reviewed
(
course
,
user
):
""" Send email when course is marked as reviewed.
Arguments:
course (Object): Course object
user (Object): User object
"""
txt_template
=
'publisher/email/course/mark_as_reviewed.txt'
html_template
=
'publisher/email/course/mark_as_reviewed.html'
subject
=
_
(
'Changes to {title} has been approved'
)
.
format
(
title
=
course
.
title
)
# pylint: disable=no-member
try
:
send_course_workflow_email
(
course
,
user
,
subject
,
txt_template
,
html_template
)
except
Exception
:
# pylint: disable=broad-except
logger
.
exception
(
'Failed to send email notifications mark as reviewed of course
%
s'
,
course
.
id
)
def
send_course_workflow_email
(
course
,
user
,
subject
,
txt_template
,
html_template
):
""" Send email for course workflow state change.
Arguments:
course (Object): Course object
user (Object): User object
subject (String): Email subject
txt_template: (String): Email text template path
html_template: (String): Email html template path
"""
recipient_user
=
course
.
marketing_reviewer
user_role
=
course
.
course_user_roles
.
get
(
user
=
user
)
if
user_role
.
role
==
PublisherUserRole
.
MarketingReviewer
:
recipient_user
=
course
.
course_team_admin
if
is_email_notification_enabled
(
recipient_user
):
partner_coordinator
=
course
.
partner_coordinator
to_addresses
=
[
recipient_user
.
email
]
from_address
=
settings
.
PUBLISHER_FROM_EMAIL
page_path
=
reverse
(
'publisher:publisher_course_detail'
,
kwargs
=
{
'pk'
:
course
.
id
})
...
...
@@ -172,6 +207,7 @@ def send_email_for_send_for_review(course, user):
'recipient_name'
:
recipient_user
.
full_name
or
recipient_user
.
username
if
recipient_user
else
''
,
'sender_name'
:
user
.
full_name
or
user
.
username
,
'course_name'
:
course
.
title
,
'contact_us_email'
:
partner_coordinator
.
email
if
partner_coordinator
else
''
,
'course_page_url'
:
'https://{host}{path}'
.
format
(
host
=
Site
.
objects
.
get_current
()
.
domain
.
strip
(
'/'
),
path
=
page_path
)
...
...
@@ -181,12 +217,8 @@ def send_email_for_send_for_review(course, user):
template
=
get_template
(
html_template
)
html_content
=
template
.
render
(
context
)
subject
=
_
(
'Changes to {title} are ready for review'
)
.
format
(
title
=
course
.
title
)
# pylint: disable=no-member
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
logger
.
exception
(
'Failed to send email notifications send for review of course
%
s'
,
course
.
id
)
course_discovery/apps/publisher/models.py
View file @
de83e87f
...
...
@@ -23,7 +23,8 @@ from course_discovery.apps.course_metadata.models import LevelType, Organization
from
course_discovery.apps.course_metadata.utils
import
UploadToFieldNamePath
from
course_discovery.apps.ietf_language_tags.models
import
LanguageTag
from
course_discovery.apps.publisher.choices
import
CourseRunStateChoices
,
CourseStateChoices
,
PublisherUserRole
from
course_discovery.apps.publisher.emails
import
send_email_for_change_state
,
send_email_for_send_for_review
from
course_discovery.apps.publisher.emails
import
(
send_email_for_change_state
,
send_email_for_mark_as_reviewed
,
send_email_for_send_for_review
)
from
course_discovery.apps.publisher.utils
import
is_email_notification_enabled
logger
=
logging
.
getLogger
(
__name__
)
...
...
@@ -207,6 +208,14 @@ class Course(TimeStampedModel, ChangedByMixin):
except
CourseUserRole
.
DoesNotExist
:
return
None
@property
def
organization_extension
(
self
):
organization
=
self
.
organizations
.
all
()
.
first
()
if
organization
:
return
organization
.
organization_extension
return
None
class
CourseRun
(
TimeStampedModel
,
ChangedByMixin
):
""" Publisher CourseRun model. It contains fields related to the course run intake form."""
...
...
@@ -554,6 +563,9 @@ class CourseState(TimeStampedModel, ChangedByMixin):
elif
state
==
CourseStateChoices
.
Approved
:
self
.
approved
()
if
waffle
.
switch_is_active
(
'enable_publisher_email_notifications'
):
send_email_for_mark_as_reviewed
(
self
.
course
,
user
)
self
.
save
()
...
...
course_discovery/apps/publisher/tests/test_emails.py
View file @
de83e87f
...
...
@@ -259,3 +259,51 @@ class CourseCreatedEmailTests(TestCase):
self
.
assertIn
(
'{dashboard_url}'
.
format
(
dashboard_url
=
reverse
(
'publisher:publisher_dashboard'
)),
body
)
self
.
assertIn
(
'Please create a Studio instance for this course'
,
body
)
self
.
assertIn
(
'Thanks'
,
body
)
class
SendForReviewEmailTests
(
TestCase
):
""" Tests for the email functionality for send for review. """
def
setUp
(
self
):
super
(
SendForReviewEmailTests
,
self
)
.
setUp
()
self
.
user
=
UserFactory
()
self
.
course_state
=
factories
.
CourseStateFactory
()
def
test_email_with_error
(
self
):
""" Verify that email failure log error message."""
with
LogCapture
(
emails
.
logger
.
name
)
as
l
:
emails
.
send_email_for_send_for_review
(
self
.
course_state
.
course
,
self
.
user
)
l
.
check
(
(
emails
.
logger
.
name
,
'ERROR'
,
'Failed to send email notifications send for review of course {}'
.
format
(
self
.
course_state
.
course
.
id
)
)
)
class
MarkAsReviewedEmailTests
(
TestCase
):
""" Tests for the email functionality for mark as reviewed. """
def
setUp
(
self
):
super
(
MarkAsReviewedEmailTests
,
self
)
.
setUp
()
self
.
user
=
UserFactory
()
self
.
course_state
=
factories
.
CourseStateFactory
()
def
test_email_with_error
(
self
):
""" Verify that email failure log error message."""
with
LogCapture
(
emails
.
logger
.
name
)
as
l
:
emails
.
send_email_for_mark_as_reviewed
(
self
.
course_state
.
course
,
self
.
user
)
l
.
check
(
(
emails
.
logger
.
name
,
'ERROR'
,
'Failed to send email notifications mark as reviewed of course {}'
.
format
(
self
.
course_state
.
course
.
id
)
)
)
course_discovery/apps/publisher/tests/test_views.py
View file @
de83e87f
...
...
@@ -672,6 +672,7 @@ class CourseRunDetailTests(TestCase):
self
.
user
.
groups
.
add
(
Group
.
objects
.
get
(
name
=
ADMIN_GROUP_NAME
))
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
USER_PASSWORD
)
self
.
course_run
=
factories
.
CourseRunFactory
(
course
=
self
.
course
)
self
.
course_run_state
=
factories
.
CourseRunStateFactory
(
course_run
=
self
.
course_run
)
self
.
organization_extension
=
factories
.
OrganizationExtensionFactory
()
self
.
course
.
organizations
.
add
(
self
.
organization_extension
.
organization
)
...
...
@@ -702,6 +703,7 @@ class CourseRunDetailTests(TestCase):
available for that course-run.
"""
course_run
=
factories
.
CourseRunFactory
(
course
=
self
.
course
)
factories
.
CourseRunStateFactory
(
course_run
=
course_run
)
assign_perm
(
OrganizationExtension
.
VIEW_COURSE_RUN
,
self
.
organization_extension
.
group
,
self
.
organization_extension
)
...
...
@@ -888,14 +890,20 @@ class CourseRunDetailTests(TestCase):
expected_roles
=
[]
for
user_course_role
in
self
.
course
.
course_user_roles
.
all
():
expected_roles
.
append
(
{
'user_course_role'
:
user_course_role
,
'heading'
:
ROLE_WIDGET_HEADINGS
.
get
(
user_course_role
.
role
)}
{
'course_role'
:
user_course_role
,
'heading'
:
ROLE_WIDGET_HEADINGS
.
get
(
user_course_role
.
role
),
'change_state_url'
:
reverse
(
'publisher:api:change_course_run_state'
,
kwargs
=
{
'pk'
:
self
.
course_run_state
.
id
}
),
'user_list'
:
get_internal_users
()
}
)
self
.
assertEqual
(
response
.
context
[
'role_widgets'
],
expected_roles
)
self
.
assertEqual
(
list
(
response
.
context
[
'user_list'
]),
list
(
get_internal_users
())
)
self
.
assertEqual
(
response
.
context
[
'role_widgets'
],
expected_roles
)
def
test_detail_page_
role_assignmen
t_with_non_internal_user
(
self
):
""" Verify that user can
't see change role assignment widget without permissions
. """
def
test_detail_page_
approval_widge
t_with_non_internal_user
(
self
):
""" Verify that user can
see change approval widget
. """
# Create a user and assign course view permission.
user
=
UserFactory
()
...
...
@@ -909,8 +917,8 @@ class CourseRunDetailTests(TestCase):
response
=
self
.
client
.
get
(
self
.
page_url
)
self
.
assert
Not
In
(
'role_widgets'
,
response
.
context
)
self
.
assert
NotIn
(
'user_list'
,
response
.
context
)
self
.
assertIn
(
'role_widgets'
,
response
.
context
)
self
.
assert
Contains
(
response
,
'APPROVALS'
)
def
test_details_page_with_edit_permission
(
self
):
""" Test that user can see edit button on course run detail page. """
...
...
@@ -1651,9 +1659,9 @@ class CourseDetailViewTests(TestCase):
# Verify that `Send for Review` button is enabled
self
.
assertContains
(
response
,
self
.
get_expected_data
(
CourseStateChoices
.
Review
))
def
test_course_
approval_widget_with
_reviewed
(
self
):
def
test_course_
with_mark_as
_reviewed
(
self
):
"""
Verify that user can see approval widget on course detail page with `Reviewed`.
Verify that user can see approval widget on course detail page with `
Mark as
Reviewed`.
"""
factories
.
CourseUserRoleFactory
(
course
=
self
.
course
,
user
=
self
.
user
,
role
=
PublisherUserRole
.
MarketingReviewer
...
...
@@ -1674,7 +1682,7 @@ class CourseDetailViewTests(TestCase):
response
=
self
.
client
.
get
(
self
.
detail_page_url
)
# Verify that content is sent for review and user can see Reviewed button.
self
.
assertContains
(
response
,
'Reviewed'
)
self
.
assertContains
(
response
,
'
Mark as
Reviewed'
)
self
.
assertContains
(
response
,
'<span class="icon fa fa-check" aria-hidden="true">'
)
self
.
assertContains
(
response
,
'Send for Review'
)
self
.
assertContains
(
response
,
self
.
get_expected_data
(
CourseStateChoices
.
Approved
))
...
...
@@ -1689,6 +1697,37 @@ class CourseDetailViewTests(TestCase):
return
expected
def
test_course_with_reviewed
(
self
):
"""
Verify that user can see approval widget on course detail page with `Reviewed`.
"""
factories
.
CourseUserRoleFactory
(
course
=
self
.
course
,
user
=
self
.
user
,
role
=
PublisherUserRole
.
MarketingReviewer
)
self
.
course_state
.
owner_role
=
PublisherUserRole
.
MarketingReviewer
self
.
course_state
.
save
()
new_user
=
UserFactory
()
factories
.
CourseUserRoleFactory
(
course
=
self
.
course
,
user
=
new_user
,
role
=
PublisherUserRole
.
CourseTeam
)
# To create history objects for both `Review` and `Approved` states
self
.
course
.
course_state
.
name
=
CourseStateChoices
.
Review
self
.
course
.
course_state
.
save
()
self
.
course
.
course_state
.
name
=
CourseStateChoices
.
Approved
self
.
course
.
course_state
.
save
()
self
.
user
.
groups
.
add
(
self
.
organization_extension
.
group
)
assign_perm
(
OrganizationExtension
.
VIEW_COURSE
,
self
.
organization_extension
.
group
,
self
.
organization_extension
)
response
=
self
.
client
.
get
(
self
.
detail_page_url
)
# Verify that content is sent for review and user can see Reviewed button.
self
.
assertNotContains
(
response
,
'Mark as Reviewed'
)
self
.
assertContains
(
response
,
'Reviewed'
,
count
=
1
)
self
.
assertContains
(
response
,
'<span class="icon fa fa-check" aria-hidden="true">'
,
count
=
2
)
self
.
assertContains
(
response
,
'Send for Review'
,
count
=
1
)
class
CourseEditViewTests
(
TestCase
):
""" Tests for the course edit view. """
...
...
@@ -1922,6 +1961,8 @@ class CourseRunEditViewTests(TestCase):
self
.
new_course
=
Course
.
objects
.
get
(
number
=
data
[
'number'
])
self
.
new_course_run
=
self
.
new_course
.
course_runs
.
first
()
factories
.
CourseRunStateFactory
(
course_run
=
self
.
new_course_run
)
# assert edit page is loading sucesfully.
self
.
edit_page_url
=
reverse
(
'publisher:publisher_course_runs_edit'
,
kwargs
=
{
'pk'
:
self
.
new_course_run
.
id
})
response
=
self
.
client
.
get
(
self
.
edit_page_url
)
...
...
course_discovery/apps/publisher/views.py
View file @
de83e87f
...
...
@@ -43,7 +43,7 @@ ROLE_WIDGET_HEADINGS = {
STATE_BUTTONS
=
{
CourseStateChoices
.
Draft
:
{
'text'
:
_
(
'Send for Review'
),
'value'
:
CourseStateChoices
.
Review
},
CourseStateChoices
.
Review
:
{
'text'
:
_
(
'Reviewed'
),
'value'
:
CourseStateChoices
.
Approved
}
CourseStateChoices
.
Review
:
{
'text'
:
_
(
'
Mark as
Reviewed'
),
'value'
:
CourseStateChoices
.
Approved
}
}
...
...
@@ -128,8 +128,12 @@ class CourseRunDetailView(mixins.LoginRequiredMixin, mixins.PublisherPermissionM
for
course_role
in
course_roles
:
role_widgets
.
append
(
{
'user_course_role'
:
course_role
,
'heading'
:
ROLE_WIDGET_HEADINGS
.
get
(
course_role
.
role
)
'course_role'
:
course_role
,
'heading'
:
ROLE_WIDGET_HEADINGS
.
get
(
course_role
.
role
),
'change_state_url'
:
reverse
(
'publisher:api:change_course_run_state'
,
kwargs
=
{
'pk'
:
self
.
object
.
course_run_state
.
id
}
),
'user_list'
:
get_internal_users
()
}
)
...
...
@@ -145,14 +149,11 @@ class CourseRunDetailView(mixins.LoginRequiredMixin, mixins.PublisherPermissionM
context
[
'post_back_url'
]
=
reverse
(
'publisher:publisher_course_run_detail'
,
kwargs
=
{
'pk'
:
course_run
.
id
})
context
[
'can_edit'
]
=
mixins
.
check_course_organization_permission
(
self
.
request
.
user
,
course_run
.
course
,
OrganizationExtension
.
EDIT_COURSE_RUN
user
,
course_run
.
course
,
OrganizationExtension
.
EDIT_COURSE_RUN
)
# Show role assignment widgets if user is an internal user.
if
is_internal_user
(
user
):
course_roles
=
course_run
.
course
.
course_user_roles
.
exclude
(
role
=
PublisherUserRole
.
CourseTeam
)
context
[
'role_widgets'
]
=
self
.
get_role_widgets_data
(
course_roles
)
context
[
'user_list'
]
=
get_internal_users
()
course_roles
=
course_run
.
course
.
course_user_roles
.
exclude
(
role
=
PublisherUserRole
.
CourseTeam
)
context
[
'role_widgets'
]
=
self
.
get_role_widgets_data
(
course_roles
)
context
[
'breadcrumbs'
]
=
make_bread_crumbs
(
[
...
...
@@ -167,7 +168,7 @@ class CourseRunDetailView(mixins.LoginRequiredMixin, mixins.PublisherPermissionM
]
)
context
[
'can_view_all_tabs'
]
=
mixins
.
check_roles_access
(
self
.
request
.
user
)
context
[
'can_view_all_tabs'
]
=
mixins
.
check_roles_access
(
user
)
context
[
'publisher_hide_features_for_pilot'
]
=
waffle
.
switch_is_active
(
'publisher_hide_features_for_pilot'
)
context
[
'publisher_comment_widget_feature'
]
=
waffle
.
switch_is_active
(
'publisher_comment_widget_feature'
)
context
[
'publisher_approval_widget_feature'
]
=
waffle
.
switch_is_active
(
'publisher_approval_widget_feature'
)
...
...
@@ -395,9 +396,18 @@ class CourseDetailView(mixins.LoginRequiredMixin, mixins.PublisherPermissionMixi
for
course_role
in
self
.
object
.
course_user_roles
.
order_by
(
'role'
):
role_widget
=
{
'course_role'
:
course_role
,
'heading'
:
ROLE_WIDGET_HEADINGS
.
get
(
course_role
.
role
)
'heading'
:
ROLE_WIDGET_HEADINGS
.
get
(
course_role
.
role
),
'change_state_url'
:
reverse
(
'publisher:api:change_course_state'
,
kwargs
=
{
'pk'
:
course_state
.
id
}
)
}
if
is_internal_user
(
user
):
role_widget
[
'user_list'
]
=
get_internal_users
()
if
course_role
.
role
==
PublisherUserRole
.
CourseTeam
:
role_widget
[
'user_list'
]
=
self
.
object
.
organization_extension
.
group
.
user_set
.
all
()
if
course_state
.
owner_role
==
course_role
.
role
:
role_widget
[
'ownership'
]
=
timezone
.
now
()
-
course_state
.
owner_role_modified
if
user
==
course_role
.
user
:
...
...
@@ -407,8 +417,18 @@ class CourseDetailView(mixins.LoginRequiredMixin, mixins.PublisherPermissionMixi
role_widget
[
'button_disabled'
]
=
True
if
course_role
.
role
in
[
PublisherUserRole
.
CourseTeam
,
PublisherUserRole
.
MarketingReviewer
]:
if
course_state
.
owner_role
!=
course_role
.
role
and
course_state
.
name
==
CourseStateChoices
.
Review
:
role_widget
[
'sent_for_review'
]
=
course_state
.
modified
if
course_state
.
owner_role
!=
course_role
.
role
:
if
course_state
.
name
!=
CourseStateChoices
.
Draft
:
history_records
=
course_state
.
history
.
filter
(
name
=
CourseStateChoices
.
Review
)
.
order_by
(
'-modified'
)
role_widget
[
'sent_for_review'
]
=
history_records
.
first
()
.
modified
if
course_state
.
name
==
CourseStateChoices
.
Approved
:
history_records
=
course_state
.
history
.
filter
(
name
=
CourseStateChoices
.
Approved
)
.
order_by
(
'-modified'
)
role_widget
[
'reviewed'
]
=
history_records
.
first
()
.
modified
role_widgets
.
append
(
role_widget
)
...
...
course_discovery/conf/locale/en/LC_MESSAGES/django.po
View file @
de83e87f
...
...
@@ -498,6 +498,11 @@ msgstr ""
msgid "Changes to {title} are ready for review"
msgstr ""
#: apps/publisher/emails.py
#, python-brace-format
msgid "Changes to {title} has been approved"
msgstr ""
#: apps/publisher/forms.py
msgid "Remove Image"
msgstr ""
...
...
@@ -747,13 +752,12 @@ msgstr ""
msgid "PUBLISHER"
msgstr ""
#: apps/publisher/views.py
#: templates/publisher/course_detail/_approval_widget.html
#: apps/publisher/views.py templates/publisher/_approval_widget.html
msgid "Send for Review"
msgstr ""
#: apps/publisher/views.py
msgid "Reviewed"
msgid "
Mark as
Reviewed"
msgstr ""
#: apps/publisher/views.py
...
...
@@ -949,6 +953,26 @@ msgstr ""
msgid "Add Staff Member"
msgstr ""
#: templates/publisher/_approval_widget.html
msgid "APPROVALS"
msgstr ""
#: templates/publisher/_approval_widget.html
msgid "Reviewed"
msgstr ""
#: templates/publisher/_approval_widget.html
msgid "day in ownership"
msgstr ""
#: templates/publisher/_approval_widget.html
msgid "change owner"
msgstr ""
#: templates/publisher/_approval_widget.html
msgid "CHANGE"
msgstr ""
#: templates/publisher/_footer.html
msgid "Terms of Service"
msgstr ""
...
...
@@ -1715,16 +1739,8 @@ msgstr ""
msgid "Course Level"
msgstr ""
#: templates/publisher/course_detail/_approval_widget.html
msgid "APPROVALS"
msgstr ""
#: templates/publisher/course_detail/_approval_widget.html
msgid "day in ownership"
msgstr ""
#: templates/publisher/course_detail/_widgets.html
#: templates/publisher/course_run_detail/_
approval_widget
.html
#: templates/publisher/course_run_detail/_
widgets
.html
msgid "EDIT"
msgstr ""
...
...
@@ -1888,14 +1904,6 @@ msgstr ""
msgid "Course Length (Weeks)"
msgstr ""
#: templates/publisher/course_run_detail/_approval_widget.html
msgid "change owner"
msgstr ""
#: templates/publisher/course_run_detail/_approval_widget.html
msgid "CHANGE"
msgstr ""
#: templates/publisher/course_run_detail/_cat.html
#: templates/publisher/course_run_detail/_drupal.html
msgid "Course ID"
...
...
@@ -2296,30 +2304,28 @@ msgstr ""
msgid "View comment: "
msgstr ""
#. Translators: partner_coordinator_name is a member name.
#: templates/publisher/email/course_created.html
#: templates/publisher/email/course/mark_as_reviewed.html
#: templates/publisher/email/course/mark_as_reviewed.txt
#: templates/publisher/email/course/send_for_review.html
#: templates/publisher/email/course/send_for_review.txt
#, python-format
msgid "Dear %(
partner_coordinator
_name)s,"
msgid "Dear %(
recipient
_name)s,"
msgstr ""
#: templates/publisher/email/course
_creat
ed.html
#: templates/publisher/email/course
/mark_as_review
ed.html
#, python-format
msgid ""
"%(course_team_name)s created the "
"%(link_start)s%(dashboard_url)s%(link_middle)s %(course_title)s %(link_end)s"
" course in Publisher on %(date)s at %(time)s."
msgstr ""
#: templates/publisher/email/course_created.html
#: templates/publisher/email/course_created.txt
msgid "Please create a Studio instance for this course."
"Changes to %(link_start)s%(course_page_url)s%(link_middle)s %(course_name)s "
"%(link_end)s has been approved."
msgstr ""
#. Translators: It's closing of mail.
#: templates/publisher/email/course/mark_as_reviewed.html
#: templates/publisher/email/course/mark_as_reviewed.txt
#: templates/publisher/email/course/send_for_review.html
#: templates/publisher/email/course/send_for_review.txt
#: templates/publisher/email/course_created.html
#: templates/publisher/email/course_created.txt
#: templates/publisher/email/send_for_review.html
#: templates/publisher/email/send_for_review.txt
#: templates/publisher/email/studio_instance_created.html
#: templates/publisher/email/studio_instance_created.txt
#: templates/publisher/email/studio_instance_needed.html
...
...
@@ -2327,26 +2333,30 @@ msgstr ""
msgid "Thanks,"
msgstr ""
#: templates/publisher/email/course_created.txt
#: templates/publisher/email/studio_instance_needed.html
#: templates/publisher/email/studio_instance_needed.txt
msgid "Dear"
#: templates/publisher/email/course/mark_as_reviewed.html
#: templates/publisher/email/course/send_for_review.html
#: templates/publisher/email/studio_instance_created.html
#, python-format
msgid ""
"<p>Note: This email address is unable to receive replies. For questions or "
"comments, contact %(contact_us_email)s.</p>"
msgstr ""
#: templates/publisher/email/course
_creat
ed.txt
#: templates/publisher/email/course
/mark_as_review
ed.txt
#, python-format
msgid ""
"%(course_team_name)s created the %(course_title)s : %(dashboard_url)s course"
" in Publisher on %(date)s at %(time)s."
msgid "Changes to %(course_name)s has been approved. %(course_page_url)s"
msgstr ""
#: templates/publisher/email/send_for_review.html
#: templates/publisher/email/send_for_review.txt
#: templates/publisher/email/course/mark_as_reviewed.txt
#: templates/publisher/email/course/send_for_review.txt
#: templates/publisher/email/studio_instance_created.txt
#, python-format
msgid "Dear %(recipient_name)s,"
msgid ""
"Note: This email address is unable to receive replies. For questions or "
"comments, contact %(contact_us_email)s."
msgstr ""
#: templates/publisher/email/send_for_review.html
#: templates/publisher/email/
course/
send_for_review.html
#, python-format
msgid ""
"New changes to %(link_start)s%(course_page_url)s%(link_middle)s "
...
...
@@ -2355,28 +2365,44 @@ msgid ""
"Publisher %(link_end)s to approve or decline the changes."
msgstr ""
#: templates/publisher/email/send_for_review.html
#: templates/publisher/email/studio_instance_created.html
#: templates/publisher/email/course/send_for_review.txt
#, python-format
msgid ""
"<p>Note: This email address is unable to receive replies. For questions or "
"comments, contact %(contact_us_email)s.</p>"
"New changes to %(course_name)s are ready for your review. "
"%(course_page_url)s View this course in Publisher to approve or decline the "
"changes."
msgstr ""
#. Translators: partner_coordinator_name is a member name.
#: templates/publisher/email/course_created.html
#, python-format
msgid "Dear %(partner_coordinator_name)s,"
msgstr ""
#: templates/publisher/email/
send_for_review.txt
#: templates/publisher/email/
course_created.html
#, python-format
msgid ""
"
New changes to %(course_name)s are ready for your review.
"
"%(
course_page_url)s View this course in Publisher to approve or decline the
"
"
change
s."
"
%(course_team_name)s created the
"
"%(
link_start)s%(dashboard_url)s%(link_middle)s %(course_title)s %(link_end)s
"
"
course in Publisher on %(date)s at %(time)
s."
msgstr ""
#: templates/publisher/email/send_for_review.txt
#: templates/publisher/email/studio_instance_created.txt
#: templates/publisher/email/course_created.html
#: templates/publisher/email/course_created.txt
msgid "Please create a Studio instance for this course."
msgstr ""
#: templates/publisher/email/course_created.txt
#: templates/publisher/email/studio_instance_needed.html
#: templates/publisher/email/studio_instance_needed.txt
msgid "Dear"
msgstr ""
#: templates/publisher/email/course_created.txt
#, python-format
msgid ""
"
Note: This email address is unable to receive replies. For questions or
"
"
comments, contact %(contact_us_email
)s."
"
%(course_team_name)s created the %(course_title)s : %(dashboard_url)s course
"
"
in Publisher on %(date)s at %(time
)s."
msgstr ""
#. Translators: course_team_name is course team member name.
...
...
course_discovery/conf/locale/eo/LC_MESSAGES/django.po
View file @
de83e87f
This diff is collapsed.
Click to expand it.
course_discovery/static/sass/publisher/publisher.scss
View file @
de83e87f
...
...
@@ -401,6 +401,7 @@
}
.btn-change-state
{
@include
padding
(
2px
,
10px
,
3px
,
10px
);
margin-top
:
15px
;
}
...
...
course_discovery/templates/publisher/
course_detail/
_approval_widget.html
→
course_discovery/templates/publisher/_approval_widget.html
View file @
de83e87f
...
...
@@ -10,15 +10,23 @@
<div
class=
"layout-1q3q layout-reversed"
>
<div
class=
"layout-col layout-col-a"
>
{% if role_widget.state_button %}
<button
class=
"btn btn-neutral btn-change-state"
data-change-state-url=
"{
% url 'publisher:api:change_course_state' object.course_state.id %
}"
data-state-name=
"{{ role_widget.state_button.value }}"
{%
if
role_widget
.
button_disabled
%}
disabled
{%
endif
%}
type=
"button"
>
<button
class=
"btn btn-neutral btn-change-state"
data-change-state-url=
"{
{ role_widget.change_state_url }
}"
data-state-name=
"{{ role_widget.state_button.value }}"
{%
if
role_widget
.
button_disabled
%}
disabled
{%
endif
%}
type=
"button"
>
{{ role_widget.state_button.text }}
</button>
{% elif role_widget.sent_for_review %}
<span
class=
"state-status"
>
<span
class=
"icon fa fa-check"
aria-hidden=
"true"
></span>
{% trans "Send for Review" %}
<br>
{{ role_widget.sent_for_review|date:'m/d/y H:i a' }}
</span>
{% else %}
{% if role_widget.sent_for_review %}
<span
class=
"state-status"
>
<span
class=
"icon fa fa-check"
aria-hidden=
"true"
></span>
{% trans "Send for Review" %}
<br>
{{ role_widget.sent_for_review|date:'m/d/y H:i a' }}
</span>
{% elif role_widget.reviewed %}
<span
class=
"state-status"
>
<span
class=
"icon fa fa-check"
aria-hidden=
"true"
></span>
{% trans "Reviewed" %}
<br>
{{ role_widget.reviewed|date:'m/d/y H:i a' }}
</span>
{% endif %}
{% endif %}
</div>
<div
class=
"layout-col layout-col-b"
>
...
...
@@ -28,12 +36,37 @@
{% if role_widget.ownership %}
<span>
{{ role_widget.ownership.days }} {% trans "day in ownership" %}
</span>
{% endif %}
<div
class=
"field-readonly user-full-name"
>
{% if role_widget.course_role.user.full_name %}
{{ role_widget.course_role.user.full_name }}
{% else %}
{{ role_widget.course_role.user.username }}
{% endif %}
<div
class=
"role-assignment-container"
>
<div
id=
"userRoleContainer-{{ role_widget.course_role.role }}"
>
<span
id=
"userFullName-{{ role_widget.course_role.role }}"
class=
"field-readonly user-full-name"
>
{% if role_widget.course_role.user.full_name %}
{{ role_widget.course_role.user.full_name }}
{% else %}
{{ role_widget.course_role.user.username }}
{% endif %}
</span>
<a
class=
"change-role-assignment"
data-role=
"{{ role_widget.course_role.role }}"
href=
"#"
>
{% trans "change owner" %}
</a>
</div>
<div
class=
"change-role-container"
id=
"changeRoleContainer-{{ role_widget.course_role.role }}"
>
<select
class=
"select-users-by-role"
id=
"selectUsers-{{ role_widget.course_role.role }}"
>
<option
value=
"-----------"
>
-----------
</option>
{% for user in role_widget.user_list %}
<option
value=
"{{ user.id }}"
>
{% if user.full_name %}
{{ user.full_name }}
{% else %}
{{ user.username }}
{% endif %}
</option>
{% endfor %}
</select>
<input
type=
"hidden"
id=
"roleName"
value=
"{{ role_widget.course_role.role }}"
>
<button
type=
"button"
class=
"btn-neutral btn-change-assignment"
data-role=
"{{ role_widget.course_role.role }}"
data-api-endpoint=
"{% url 'publisher:api:course_role_assignments' role_widget.course_role.id %}"
>
{% trans "CHANGE" %}
</button>
</div>
</div>
</div>
</div>
...
...
course_discovery/templates/publisher/course_detail.html
View file @
de83e87f
...
...
@@ -132,6 +132,7 @@
{% endblock %}
{% block extra_js %}
<script
src=
"{% static 'bower_components/google-diff-match-patch/diff_match_patch.js' %}"
></script>
<script
src=
"{% static 'js/publisher/views/course_detail.js' %}"
></script>
<script
src=
"{% static 'js/publisher/publisher.js' %}"
></script>
<script
src=
"{% static 'js/publisher/comparing-objects.js' %}"
></script>
<script
src=
"{% static 'js/publisher/jquery-dateFormat.min.js' %}"
></script>
...
...
course_discovery/templates/publisher/course_detail/_widgets.html
View file @
de83e87f
...
...
@@ -43,6 +43,6 @@
{% include 'publisher/_history_widget.html' %}
</div>
<div
class=
"approval-widget {% if not publisher_approval_widget_feature %}hidden{% endif %}"
>
{% include 'publisher/
course_detail/
_approval_widget.html' %}
{% include 'publisher/_approval_widget.html' %}
</div>
</div>
course_discovery/templates/publisher/course_run_detail.html
View file @
de83e87f
...
...
@@ -68,7 +68,7 @@
<aside
id=
"right-panel"
class=
"layout-col layout-col-a layout-col-a-custom"
>
<div
id=
"approval-widget"
class=
"approval-widget {% if not publisher_approval_widget_feature %}hidden{% endif %}"
>
{% include 'publisher/course_run_detail/_
approval_widget
.html' %}
{% include 'publisher/course_run_detail/_
widgets
.html' %}
</div>
<div
id=
"comments-widget"
class=
"comment-container {% if not publisher_comment_widget_feature %}hidden{% endif %}"
>
{% include 'comments/add_auth_comments.html' %}
...
...
course_discovery/templates/publisher/course_run_detail/_approval_widget.html
deleted
100644 → 0
View file @
a297c8e0
{% load i18n %}
<div
class=
"approval-widget"
>
{% if can_edit %}
<a
href=
"{% url 'publisher:publisher_course_runs_edit' pk=object.id %}"
class=
"btn btn-neutral btn-courserun-edit"
>
{% trans "EDIT" %}
</a>
<div
class=
"clearfix"
></div>
{% endif %}
{% for role_widget in role_widgets %}
<div
class=
"role-widget"
>
<span
class=
"role-heading"
>
<strong>
{{ role_widget.heading }}
</strong>
</span>
<div
class=
"role-assignment-container"
>
<div
id=
"userRoleContainer-{{ role_widget.user_course_role.role }}"
>
<span
id=
"userFullName-{{ role_widget.user_course_role.role }}"
class=
"field-readonly user-full-name"
>
{% if role_widget.user_course_role.user.full_name %}
{{ role_widget.user_course_role.user.full_name }}
{% else %}
{{ role_widget.user_course_role.user.username }}
{% endif %}
</span>
<a
class=
"change-role-assignment"
data-role=
"{{ role_widget.user_course_role.role }}"
href=
"#"
>
{% trans "change owner" %}
</a>
</div>
<div
class=
"change-role-container"
id=
"changeRoleContainer-{{ role_widget.user_course_role.role }}"
>
<select
class=
"select-users-by-role"
id=
"selectUsers-{{ role_widget.user_course_role.role }}"
>
{% for user in user_list %}
<option
{%
if
role_widget
.
user_course_role
.
user =
=
user
%}
selected=
"selected"
{%
endif
%}
value=
"{{ user.id }}"
>
{{ user.full_name }}
</option>
{% endfor %}
</select>
<input
type=
"hidden"
id=
"roleName"
value=
"{{ role_widget.user_course_role.role }}"
>
<button
type=
"button"
class=
"btn-neutral btn-change-assignment"
data-role=
"{{ role_widget.user_course_role.role }}"
data-api-endpoint=
"{% url 'publisher:api:course_role_assignments' role_widget.user_course_role.id %}"
>
{% trans "CHANGE" %}
</button>
</div>
</div>
</div>
<hr>
{% endfor %}
<div
class=
"actions"
>
<form
action=
"{% url 'publisher:publisher_change_state' pk=object.id %}"
method=
"post"
>
{% csrf_token %}
<button
type=
"submit"
value=
"{{ object.change_state_button.value }}"
class=
"btn-brand btn-small btn-states"
name=
"state"
>
{{ object.change_state_button.text }}
</button>
</form>
</div>
</div>
course_discovery/templates/publisher/course_run_detail/_widgets.html
0 → 100644
View file @
de83e87f
{% load i18n %}
<div
class=
"course-widgets"
>
{% if can_edit %}
<a
href=
"{% url 'publisher:publisher_course_runs_edit' pk=object.id %}"
class=
"btn btn-neutral btn-courserun-edit"
>
{% trans "EDIT" %}
</a>
<div
class=
"clearfix"
></div>
{% endif %}
<div
class=
"approval-widget {% if not publisher_approval_widget_feature %}hidden{% endif %}"
>
{% include 'publisher/_approval_widget.html' %}
</div>
</div>
course_discovery/templates/publisher/email/course/mark_as_reviewed.html
0 → 100644
View file @
de83e87f
{% extends "publisher/email/email_base.html" %}
{% load i18n %}
{% block body %}
<!-- Message Body -->
<p>
{% blocktrans trimmed %}
Dear {{ recipient_name }},
{% endblocktrans %}
<p>
{% blocktrans with link_start='
<a
href=
"' link_middle='"
>
' link_end='
</a>
' trimmed %}
Changes to {{ link_start }}{{ course_page_url }}{{ link_middle }} {{ course_name }} {{ link_end }} has been approved.
{% endblocktrans %}
</p>
{% comment %}Translators: It's closing of mail.{% endcomment %}
{% trans "Thanks," %}
<br>
{{ sender_name }}
{% blocktrans trimmed %}
<p>
Note: This email address is unable to receive replies. For questions or comments, contact {{ contact_us_email }}.
</p>
{% endblocktrans %}
<!-- End Message Body -->
{% endblock body %}
course_discovery/templates/publisher/email/course/mark_as_reviewed.txt
0 → 100644
View file @
de83e87f
{% load i18n %}
{% blocktrans trimmed %}
Dear {{ recipient_name }},
{% endblocktrans %}
{% blocktrans trimmed %}
Changes to {{ course_name }} has been approved. {{ course_page_url }}
{% endblocktrans %}
{% trans "Thanks," %}
{{ sender_name }}
{% blocktrans trimmed %}
Note: This email address is unable to receive replies. For questions or comments, contact {{ contact_us_email }}.
{% endblocktrans %}
course_discovery/templates/publisher/email/send_for_review.html
→
course_discovery/templates/publisher/email/
course/
send_for_review.html
View file @
de83e87f
File moved
course_discovery/templates/publisher/email/send_for_review.txt
→
course_discovery/templates/publisher/email/
course/
send_for_review.txt
View file @
de83e87f
File moved
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment