Commit 57a8c10d by Waheed Ahmed

Change course ownership on edit also added warning popup.

ECOM-7415
parent 3bfc68b9
......@@ -548,6 +548,14 @@ class CourseState(TimeStampedModel, ChangedByMixin):
""" Check that course is approved or not."""
return self.name == CourseStateChoices.Approved
def change_owner_role(self, role):
"""
Change ownership role.
"""
self.owner_role = role
self.owner_role_modified = timezone.now()
self.save()
class CourseRunState(TimeStampedModel, ChangedByMixin):
""" Publisher Workflow Course Run State Model. """
......
......@@ -507,6 +507,17 @@ class CourseStateTests(TestCase):
self.assertFalse(self.course_state.can_send_for_review())
@ddt.data(
PublisherUserRole.MarketingReviewer,
PublisherUserRole.CourseTeam,
)
def test_change_owner_role(self, role):
"""
Verify that method change_owner_role updates the role.
"""
self.course_state.change_owner_role(role)
self.assertEqual(self.course_state.owner_role, role)
@ddt.ddt
class CourseRunStateTests(TestCase):
......
......@@ -7,18 +7,16 @@ from guardian.shortcuts import assign_perm
from mock import Mock
from course_discovery.apps.core.tests.factories import UserFactory
from course_discovery.apps.publisher.constants import (
ADMIN_GROUP_NAME, INTERNAL_USER_GROUP_NAME, PROJECT_COORDINATOR_GROUP_NAME, REVIEWER_GROUP_NAME
)
from course_discovery.apps.publisher.mixins import (
check_course_organization_permission, check_roles_access, publisher_user_required
)
from course_discovery.apps.publisher.constants import (ADMIN_GROUP_NAME, INTERNAL_USER_GROUP_NAME,
PROJECT_COORDINATOR_GROUP_NAME, REVIEWER_GROUP_NAME)
from course_discovery.apps.publisher.mixins import (check_course_organization_permission, check_roles_access,
publisher_user_required)
from course_discovery.apps.publisher.models import OrganizationExtension
from course_discovery.apps.publisher.tests import factories
from course_discovery.apps.publisher.utils import (
get_internal_users, is_email_notification_enabled, is_internal_user, is_project_coordinator_user,
is_publisher_admin, is_publisher_user, make_bread_crumbs
)
from course_discovery.apps.publisher.utils import (get_internal_users, has_role_for_course,
is_email_notification_enabled, is_internal_user,
is_project_coordinator_user, is_publisher_admin, is_publisher_user,
make_bread_crumbs)
@ddt.ddt
......@@ -197,3 +195,12 @@ class PublisherUtilsTests(TestCase):
[{'url': '/publisher/courses/new/', 'slug': 'Courses'}, {'url': None, 'slug': 'Testing'}],
make_bread_crumbs(links)
)
def test_has_role_for_course(self):
"""
Verify the function returns a boolean indicating if the user has a role for course.
"""
self.assertFalse(has_role_for_course(self.course, self.user))
factories.CourseUserRoleFactory(course=self.course, user=self.user)
self.assertTrue(has_role_for_course(self.course, self.user))
......@@ -1479,6 +1479,10 @@ class CourseDetailViewTests(TestCase):
# Initialize workflow for Course.
self.course_state = factories.CourseStateFactory(course=self.course, owner_role=PublisherUserRole.CourseTeam)
self.course_team_role = factories.CourseUserRoleFactory(
course=self.course, user=self.user, role=PublisherUserRole.CourseTeam
)
self.detail_page_url = reverse('publisher:publisher_course_detail', args=[self.course.id])
def test_detail_page_without_permission(self):
......@@ -1558,9 +1562,6 @@ class CourseDetailViewTests(TestCase):
"""
Verify that user can see course details on detail page.
"""
factories.CourseUserRoleFactory(
course=self.course, user=self.user, role=PublisherUserRole.CourseTeam
)
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)
......@@ -1657,9 +1658,6 @@ class CourseDetailViewTests(TestCase):
"""
Verify that user can see approval widget on course detail page.
"""
factories.CourseUserRoleFactory(
course=self.course, user=self.user, role=PublisherUserRole.CourseTeam
)
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)
......@@ -1690,9 +1688,8 @@ class CourseDetailViewTests(TestCase):
self.course_state.name = CourseStateChoices.Review
self.course_state.save()
factories.CourseUserRoleFactory(
course=self.course, user=UserFactory(), role=PublisherUserRole.CourseTeam
)
self.course_team_role.user = UserFactory()
self.course_team_role.save()
self.user.groups.add(self.organization_extension.group)
assign_perm(OrganizationExtension.VIEW_COURSE, self.organization_extension.group, self.organization_extension)
......@@ -1718,12 +1715,14 @@ class CourseDetailViewTests(TestCase):
"""
Verify that user can see approval widget on course detail page with `Reviewed`.
"""
self.course_team_role.user = UserFactory()
self.course_team_role.save()
factories.CourseUserRoleFactory(
course=self.course, user=self.user, role=PublisherUserRole.MarketingReviewer
)
factories.CourseUserRoleFactory(
course=self.course, user=UserFactory(), role=PublisherUserRole.CourseTeam
course=self.course, user=UserFactory(), role=PublisherUserRole.Publisher
)
# To create history objects for both `Review` and `Approved` states
......@@ -1744,6 +1743,31 @@ class CourseDetailViewTests(TestCase):
self.assertContains(response, '<span class="icon fa fa-check" aria-hidden="true">', count=2)
self.assertContains(response, 'Send for Review', count=1)
def test_edit_permission_with_owner_role(self):
"""
Test that user can see edit button if he has permission and has role for course.
"""
self.course_state.owner_role = PublisherUserRole.MarketingReviewer
self.course_state.save()
factories.CourseUserRoleFactory(
course=self.course, user=UserFactory(), role=PublisherUserRole.MarketingReviewer
)
self.user.groups.add(self.organization_extension.group)
assign_perm(OrganizationExtension.VIEW_COURSE, self.organization_extension.group, self.organization_extension)
assign_perm(OrganizationExtension.EDIT_COURSE, self.organization_extension.group, self.organization_extension)
# Verify that user can see edit button with edit permission.
self.assert_can_edit_permission(can_edit=True)
# Assign new user to course team role.
self.course_team_role.user = UserFactory()
self.course_team_role.save()
# Verify that user cannot see edit button if he has no role for course.
self.assert_can_edit_permission(can_edit=False)
class CourseEditViewTests(TestCase):
""" Tests for the course edit view. """
......@@ -1935,6 +1959,35 @@ class CourseEditViewTests(TestCase):
course_state = CourseState.objects.get(id=self.course.course_state.id)
self.assertEqual(course_state.name, CourseStateChoices.Draft)
def test_edit_course_with_ownership_changed(self):
"""
Verify that on editing course state changed to `Draft` and ownership changed
to `CourseTeam` if course team user updating the course.
"""
self.client.logout()
self.client.login(username=self.course_team_role.user.username, password=USER_PASSWORD)
self._assign_permissions(self.organization_extension)
self.course.course_state.name = CourseStateChoices.Review
self.course.course_state.owner_role = PublisherUserRole.MarketingReviewer
self.course.course_state.save()
post_data = self._post_data(self.organization_extension)
post_data['team_admin'] = self.course_team_role.user.id
response = self.client.post(self.edit_page_url, data=post_data)
self.assertRedirects(
response,
expected_url=reverse('publisher:publisher_course_detail', kwargs={'pk': self.course.id}),
status_code=302,
target_status_code=200
)
course_state = CourseState.objects.get(id=self.course.course_state.id)
self.assertEqual(course_state.name, CourseStateChoices.Draft)
self.assertEqual(course_state.owner_role, PublisherUserRole.CourseTeam)
@ddt.ddt
class CourseRunEditViewTests(TestCase):
......
......@@ -94,3 +94,17 @@ def make_bread_crumbs(links):
}
for url, slug in links
]
def has_role_for_course(course, user):
"""
Check user has a role for course.
Arguments:
course: Course object
user: User object
Returns:
bool: True, if user has a role for course; otherwise, False.
"""
return course.course_user_roles.filter(user=user).exists()
......@@ -23,8 +23,8 @@ from course_discovery.apps.publisher.choices import CourseRunStateChoices, Cours
from course_discovery.apps.publisher.forms import CustomCourseForm, CustomCourseRunForm, CustomSeatForm
from course_discovery.apps.publisher.models import (Course, CourseRun, CourseRunState, CourseState, CourseUserRole,
OrganizationExtension, UserAttributes)
from course_discovery.apps.publisher.utils import (get_internal_users, is_internal_user, is_project_coordinator_user,
is_publisher_admin, make_bread_crumbs)
from course_discovery.apps.publisher.utils import (get_internal_users, has_role_for_course, is_internal_user,
is_project_coordinator_user, is_publisher_admin, make_bread_crumbs)
from course_discovery.apps.publisher.wrappers import CourseRunWrapper
logger = logging.getLogger(__name__)
......@@ -352,13 +352,13 @@ class CourseEditView(mixins.PublisherPermissionMixin, UpdateView):
course_admin_role.user = team_admin
course_admin_role.save()
user_role = self.object.course_user_roles.get(user=user)
self.object.course_state.owner_role = user_role.role
if self.object.course_state.name != CourseStateChoices.Draft:
self.object.course_state.change_state(state=CourseStateChoices.Draft, user=user)
self.object.course_state.save()
# Change ownership if user role not equal to owner role.
user_role = self.object.course_user_roles.get(user=user)
if self.object.course_state.owner_role != user_role.role:
self.object.course_state.change_owner_role(user_role.role)
messages.success(self.request, _('Course updated successfully.'))
return HttpResponseRedirect(self.get_success_url())
......@@ -377,7 +377,7 @@ class CourseDetailView(mixins.LoginRequiredMixin, mixins.PublisherPermissionMixi
course = self.object
context['can_edit'] = mixins.check_course_organization_permission(
user, course, OrganizationExtension.EDIT_COURSE
)
) and has_role_for_course(course, user)
context['breadcrumbs'] = make_bread_crumbs(
[
......@@ -395,6 +395,19 @@ class CourseDetailView(mixins.LoginRequiredMixin, mixins.PublisherPermissionMixi
user, course, course.course_state, 'publisher:api:change_course_state'
)
# Add warning popup information if user can edit the course but does not own it.
if context['can_edit']:
current_owner_role = course.course_user_roles.get(role=course.course_state.owner_role)
user_role = course.course_user_roles.get(user=user)
if user_role.role != current_owner_role.role:
context['add_warning_popup'] = True
context['current_team_name'] = (_('course')
if current_owner_role.role == PublisherUserRole.CourseTeam
else _('marketing'))
context['team_name'] = (_('course')
if current_owner_role.role == PublisherUserRole.MarketingReviewer
else _('marketing'))
return context
......
......@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-03-20 12:29+0500\n"
"POT-Creation-Date: 2017-03-20 14:01+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
......@@ -803,6 +803,14 @@ msgid "Course updated successfully."
msgstr ""
#: apps/publisher/views.py
msgid "course"
msgstr ""
#: apps/publisher/views.py
msgid "marketing"
msgstr ""
#: apps/publisher/views.py
#, python-brace-format
msgid "Course run created successfully for course \"{course_title}\"."
msgstr ""
......@@ -898,6 +906,7 @@ msgstr ""
#: templates/metadata/admin/course_run.html
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/add_update_course_form.html
#: templates/publisher/course_detail/_edit_warning_popup.html
#: templates/publisher/course_edit_form.html
#: templates/publisher/course_run_detail/_preview_accept_popup.html
msgid "Cancel"
......@@ -1849,6 +1858,24 @@ msgstr ""
msgid "Course Level"
msgstr ""
#: templates/publisher/course_detail/_edit_warning_popup.html
msgid "CAUTION"
msgstr ""
#: templates/publisher/course_detail/_edit_warning_popup.html
#, python-format
msgid ""
"\n"
" The %(current_team_name)s team is currently reviewing this course. If you edit course information, you might overwrite the team’s changes, and you will have to send the course to the %(team_name)s team for review again.\n"
" "
msgstr ""
#: templates/publisher/course_detail/_edit_warning_popup.html
#: templates/publisher/course_run_detail/_widgets.html
#: templates/publisher/courses.html
msgid "Edit"
msgstr ""
#: templates/publisher/course_detail/_widgets.html
#: templates/publisher/course_run_detail/_widgets.html
msgid "EDIT"
......@@ -2255,11 +2282,6 @@ msgid "Submitted for review"
msgstr ""
#: templates/publisher/course_run_detail/_widgets.html
#: templates/publisher/courses.html
msgid "Edit"
msgstr ""
#: templates/publisher/course_run_detail/_widgets.html
msgid "COURSE PREVIEW"
msgstr ""
......
......@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-03-20 12:29+0500\n"
"POT-Creation-Date: 2017-03-20 14:02+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-03-20 12:29+0500\n"
"POT-Creation-Date: 2017-03-20 14:01+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
......@@ -955,6 +955,14 @@ msgid "Course updated successfully."
msgstr "Çöürsé üpdätéd süççéssfüllý. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢#"
#: apps/publisher/views.py
msgid "course"
msgstr "çöürsé Ⱡ'σяєм ιρѕυ#"
#: apps/publisher/views.py
msgid "marketing"
msgstr "märkétïng Ⱡ'σяєм ιρѕυм ∂σł#"
#: apps/publisher/views.py
#, python-brace-format
msgid "Course run created successfully for course \"{course_title}\"."
msgstr ""
......@@ -1059,6 +1067,7 @@ msgstr "Sävé Ⱡ'σяєм ι#"
#: templates/metadata/admin/course_run.html
#: templates/publisher/_add_instructor_popup.html
#: templates/publisher/add_update_course_form.html
#: templates/publisher/course_detail/_edit_warning_popup.html
#: templates/publisher/course_edit_form.html
#: templates/publisher/course_run_detail/_preview_accept_popup.html
msgid "Cancel"
......@@ -2216,6 +2225,27 @@ msgstr "Çöürsé Ìmägé Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
msgid "Course Level"
msgstr "Çöürsé Lévél Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
#: templates/publisher/course_detail/_edit_warning_popup.html
msgid "CAUTION"
msgstr "ÇÀÛTÌÖN Ⱡ'σяєм ιρѕυм #"
#: templates/publisher/course_detail/_edit_warning_popup.html
#, python-format
msgid ""
"\n"
" The %(current_team_name)s team is currently reviewing this course. If you edit course information, you might overwrite the team’s changes, and you will have to send the course to the %(team_name)s team for review again.\n"
" "
msgstr ""
"\n"
" Thé %(current_team_name)s téäm ïs çürréntlý révïéwïng thïs çöürsé. Ìf ýöü édït çöürsé ïnförmätïön, ýöü mïght övérwrïté thé téäm’s çhängés, änd ýöü wïll hävé tö sénd thé çöürsé tö thé %(team_name)s téäm för révïéw ägäïn.\n"
" Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α∂ιριѕι¢ιηg єłιт, ѕє∂ ∂σ єιυѕмσ∂ тємρσя ιη¢ι∂ι∂υηт υт łαвσяє єт ∂σłσяє мαgηα αłιqυα. υт єηιм α∂ мιηιм νєηιαм, qυιѕ ησѕтяυ∂ єχєя¢ιтαтιση υłłαм¢σ łαвσяιѕ ηιѕι υт αłιqυιρ єχ єα ¢σммσ∂σ ¢σηѕєqυαт. ∂υιѕ αυтє ιяυяє ∂σłσя ιη яєρяєнєη∂єяιт ιη νσłυρтαтє νєłιт єѕѕє ¢ιłłυм ∂σłσяє єυ ƒυgιαт ηυłłα#"
#: templates/publisher/course_detail/_edit_warning_popup.html
#: templates/publisher/course_run_detail/_widgets.html
#: templates/publisher/courses.html
msgid "Edit"
msgstr "Édït Ⱡ'σяєм ι#"
#: templates/publisher/course_detail/_widgets.html
#: templates/publisher/course_run_detail/_widgets.html
msgid "EDIT"
......@@ -2641,11 +2671,6 @@ msgid "Submitted for review"
msgstr "Süßmïttéd för révïéw Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, #"
#: templates/publisher/course_run_detail/_widgets.html
#: templates/publisher/courses.html
msgid "Edit"
msgstr "Édït Ⱡ'σяєм ι#"
#: templates/publisher/course_run_detail/_widgets.html
msgid "COURSE PREVIEW"
msgstr "ÇÖÛRSÉ PRÉVÌÉW Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт#"
......
......@@ -7,14 +7,14 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2017-03-20 12:29+0500\n"
"POT-Creation-Date: 2017-03-20 14:02+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
......
......@@ -155,6 +155,11 @@ $(document).ready(function(){
}
});
});
$('.btn-course-edit').click(function(e){
$('#editWarningModal').show();
$('body').addClass('stopScroll');
});
});
$(document).on('change', '#id_organization', function (e) {
......
{% load i18n %}
<div id="editWarningModal" class="modal">
<div class="modal-content">
<h2 class="hd-2 emphasized">
<span class="icon fa fa-exclamation-triangle" aria-hidden="true"></span> {% trans "CAUTION" %}
</h2>
<span class="sr-only">CAUTION</span>
<p class="margin-top20">
{% blocktrans%}
The {{ current_team_name }} team is currently reviewing this course. If you edit course information, you might overwrite the team’s changes, and you will have to send the course to the {{ team_name }} team for review again.
{% endblocktrans %}
</p>
<div class="actions">
<a class="btn-cancel closeModal" href="#">{% trans "Cancel" %}</a>
<a class="btn-brand btn-base btn-accept" href="{% url 'publisher:publisher_courses_edit' pk=object.id %}">{% trans "Edit" %}</a>
</div>
</div>
</div>
......@@ -2,7 +2,8 @@
<div class="course-widgets">
{% if can_edit %}
<a href="{% url 'publisher:publisher_courses_edit' pk=object.id %}" class="btn btn-neutral btn-courserun-edit">
{% url 'publisher:publisher_courses_edit' pk=object.id as edit_page_url %}
<a href="{% if add_warning_popup %}#{% else %}{{ edit_page_url }}{% endif %}" class="btn btn-neutral btn-course-edit">
{% trans "EDIT" %}
</a>
<div class="clearfix"></div>
......@@ -46,3 +47,7 @@
{% include 'publisher/_approval_widget.html' %}
</div>
</div>
{% if add_warning_popup %}
{% include 'publisher/course_detail/_edit_warning_popup.html' %}
{% endif %}
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