Commit fea4a3f0 by Waheed Ahmed

Implemented change course role assignment.

ECOM-6207
parent 5b78c872
from django.contrib import admin
from course_discovery.apps.publisher.models import (
Course, CourseRun, OrganizationUserRole, Seat, State, UserAttributes
Course, CourseRun, CourseUserRole, OrganizationUserRole, Seat, State, UserAttributes
)
admin.site.register(Course)
......@@ -10,3 +10,8 @@ admin.site.register(OrganizationUserRole)
admin.site.register(Seat)
admin.site.register(State)
admin.site.register(UserAttributes)
@admin.register(CourseUserRole)
class CourseUserRoleAdmin(admin.ModelAdmin):
raw_id_fields = ('user',)
from rest_framework.permissions import BasePermission
from course_discovery.apps.publisher.mixins import check_view_permission
from course_discovery.apps.publisher.utils import is_internal_user
class CanViewAssociatedCourse(BasePermission):
""" Permission class to check user can view a publisher course. """
def has_object_permission(self, request, view, obj):
return check_view_permission(request.user, obj.course)
class InternalUserPermission(BasePermission):
""" Permission class to check user is an internal user. """
def has_object_permission(self, request, view, obj):
return is_internal_user(request.user)
"""Publisher API Serializers"""
from rest_framework import serializers
from course_discovery.apps.publisher.models import CourseUserRole
class CourseUserRoleSerializer(serializers.ModelSerializer):
"""Serializer for the `CourseUserRole` model to change role assignment. """
class Meta:
model = CourseUserRole
fields = ('course', 'user', 'role',)
read_only_fields = ('course', 'role')
def validate(self, data):
validated_values = super(CourseUserRoleSerializer, self).validate(data)
request = self.context.get('request')
if request:
validated_values.update({'changed_by': request.user})
return validated_values
"""Tests API Serializers."""
from unittest import TestCase
from django.test import RequestFactory
from course_discovery.apps.publisher.api.serializers import CourseUserRoleSerializer
from course_discovery.apps.publisher.tests.factories import CourseUserRoleFactory
class CourseUserRoleSerializerTests(TestCase):
serializer_class = CourseUserRoleSerializer
def setUp(self):
super(CourseUserRoleSerializerTests, self).setUp()
self.request = RequestFactory()
self.course_user_role = CourseUserRoleFactory()
self.request.user = self.course_user_role.user
def get_expected_data(self):
return {
'course': self.course_user_role.course.id,
'user': self.course_user_role.user.id,
'role': self.course_user_role.role,
'changed_by': self.course_user_role.user
}
def test_validation(self):
serializer = self.serializer_class(self.course_user_role, context={'request': self.request})
validated_data = serializer.validate(serializer.data)
self.assertEqual(validated_data, self.get_expected_data())
# pylint: disable=no-member
import json
import ddt
from django.contrib.auth.models import Group
from django.core.urlresolvers import reverse
from django.test import TestCase
from guardian.shortcuts import assign_perm
from course_discovery.apps.core.tests.factories import UserFactory, USER_PASSWORD
from course_discovery.apps.course_metadata.tests.factories import OrganizationFactory
from course_discovery.apps.publisher.choices import PublisherUserRole
from course_discovery.apps.publisher.constants import INTERNAL_USER_GROUP_NAME
from course_discovery.apps.publisher.models import Course
from course_discovery.apps.publisher.tests import factories, JSON_CONTENT_TYPE
@ddt.ddt
class CourseRoleAssignmentViewTests(TestCase):
def setUp(self):
super(CourseRoleAssignmentViewTests, self).setUp()
self.course = factories.CourseFactory()
# Create an internal user group and assign four users.
self.internal_user = UserFactory()
internal_user_group = Group.objects.get(name=INTERNAL_USER_GROUP_NAME)
internal_user_group.user_set.add(self.internal_user)
self.other_internal_users = []
for __ in range(3):
user = UserFactory()
self.other_internal_users.append(user)
internal_user_group.user_set.add(user)
assign_perm(Course.VIEW_PERMISSION, internal_user_group, self.course)
organization = OrganizationFactory()
self.course.organizations.add(organization)
# Create three internal user course roles for internal users against a course
# so we can test change role assignment on these roles.
for user, role in zip(self.other_internal_users, PublisherUserRole.choices):
factories.CourseUserRoleFactory(course=self.course, user=user, role=role)
self.client.login(username=self.internal_user.username, password=USER_PASSWORD)
def get_role_assignment_url(self, user_course_role):
return reverse(
'publisher:api:course_role_assignments', kwargs={'pk': user_course_role.id}
)
def test_role_assignment_with_non_internal_user(self):
""" Verify non-internal users cannot change role assignments. """
non_internal_user = UserFactory()
assign_perm(Course.VIEW_PERMISSION, non_internal_user, self.course)
self.client.logout()
self.client.login(username=non_internal_user.username, password=USER_PASSWORD)
response = self.client.patch(
self.get_role_assignment_url(self.course.course_user_roles.first()),
data=json.dumps({'user': non_internal_user.id}),
content_type=JSON_CONTENT_TYPE
)
self.assertEqual(response.status_code, 403)
def get_user_course_roles(self):
return self.course.course_user_roles.all()
@ddt.data(
PublisherUserRole.PartnerCoordinator,
PublisherUserRole.MarketingReviewer,
PublisherUserRole.Publisher
)
def test_change_role_assignment_with_internal_user(self, role_name):
""" Verify that internal user can change course role assignment for
all three internal user course roles to another internal user.
"""
user_course_role = self.course.course_user_roles.get(role__icontains=role_name)
response = self.client.patch(
self.get_role_assignment_url(user_course_role),
data=json.dumps({'user': self.internal_user.id}),
content_type=JSON_CONTENT_TYPE
)
self.assertEqual(response.status_code, 200)
expected = {
'course': self.course.id,
'user': self.internal_user.id,
'role': user_course_role.role
}
self.assertDictEqual(response.data, expected)
self.assertEqual(self.internal_user, self.course.course_user_roles.get(role=user_course_role.role).user)
""" Publisher API URLs. """
from django.conf.urls import url
from course_discovery.apps.publisher.api.views import CourseRoleAssignmentView
urlpatterns = [
url(
r'^course_role_assignments/(?P<pk>\d+)/$', CourseRoleAssignmentView.as_view(), name='course_role_assignments'
),
]
from rest_framework.generics import UpdateAPIView
from rest_framework.permissions import IsAuthenticated
from course_discovery.apps.publisher.models import CourseUserRole
from course_discovery.apps.publisher.api.permissions import CanViewAssociatedCourse, InternalUserPermission
from course_discovery.apps.publisher.api.serializers import CourseUserRoleSerializer
class CourseRoleAssignmentView(UpdateAPIView):
permission_classes = (IsAuthenticated, CanViewAssociatedCourse, InternalUserPermission,)
queryset = CourseUserRole.objects.all()
serializer_class = CourseUserRoleSerializer
# Name of the administrative group for the Publisher app
ADMIN_GROUP_NAME = 'Publisher Admins'
INTERNAL_USER_GROUP_NAME = 'Internal Users'
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations
from course_discovery.apps.publisher.constants import INTERNAL_USER_GROUP_NAME
def create_internal_user_group(apps, schema_editor):
Group = apps.get_model('auth', 'Group')
Group.objects.get_or_create(name=INTERNAL_USER_GROUP_NAME)
def remove_internal_user_group(apps, schema_editor):
Group = apps.get_model('auth', 'Group')
Group.objects.filter(name=INTERNAL_USER_GROUP_NAME).delete()
class Migration(migrations.Migration):
dependencies = [
('publisher', '0017_auto_20161201_1501'),
('auth', '0006_require_contenttypes_0002'),
]
operations = [
migrations.RunPython(create_internal_user_group, remove_internal_user_group)
]
"""Publisher Serializers"""
import waffle
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
......
......@@ -28,8 +28,7 @@ class UpdateCourseKeySerializerTests(TestCase):
def test_validation(self):
self.course_run.lms_course_id = 'course-v1:edxTest+TC101+2016_Q1'
self.course_run.save() # pylint: disable=no-member
serializer = self.serializer_class(self.course_run)
serializer.context['request'] = self.request
serializer = self.serializer_class(self.course_run, context={'request': self.request})
expected = serializer.validate(serializer.data)
self.assertEqual(self.get_expected_data(), expected)
......
......@@ -3,9 +3,11 @@ from django.contrib.auth.models import Group
from django.test import TestCase
from course_discovery.apps.core.tests.factories import UserFactory
from course_discovery.apps.publisher.constants import ADMIN_GROUP_NAME
from course_discovery.apps.publisher.constants import ADMIN_GROUP_NAME, INTERNAL_USER_GROUP_NAME
from course_discovery.apps.publisher.tests import factories
from course_discovery.apps.publisher.utils import is_email_notification_enabled, is_publisher_admin
from course_discovery.apps.publisher.utils import (
is_email_notification_enabled, is_publisher_admin, is_internal_user, get_internal_users
)
class PublisherUtilsTests(TestCase):
......@@ -49,3 +51,21 @@ class PublisherUtilsTests(TestCase):
admin_group = Group.objects.get(name=ADMIN_GROUP_NAME)
self.user.groups.add(admin_group)
self.assertTrue(is_publisher_admin(self.user))
def test_is_internal_user(self):
""" Verify the function returns a boolean indicating if the user
is a member of the internal user group.
"""
self.assertFalse(is_internal_user(self.user))
internal_user_group = Group.objects.get(name=INTERNAL_USER_GROUP_NAME)
self.user.groups.add(internal_user_group)
self.assertTrue(is_internal_user(self.user))
def test_get_internal_user(self):
""" Verify the function returns all internal users. """
internal_user_group = Group.objects.get(name=INTERNAL_USER_GROUP_NAME)
self.assertEqual(get_internal_users(), [])
self.user.groups.add(internal_user_group)
self.assertEqual(get_internal_users(), [self.user])
......@@ -8,6 +8,7 @@ from mock import patch
from django.db import IntegrityError
from django.conf import settings
from django.contrib.auth.models import Group
from django.contrib.sites.models import Site
from django.core import mail
from django.core.urlresolvers import reverse
......@@ -20,11 +21,16 @@ from course_discovery.apps.core.models import User
from course_discovery.apps.core.tests.factories import UserFactory, USER_PASSWORD
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 OrganizationFactory
from course_discovery.apps.publisher.choices import PublisherUserRole
from course_discovery.apps.publisher.constants import INTERNAL_USER_GROUP_NAME
from course_discovery.apps.publisher.models import Course, CourseRun, Seat, State
from course_discovery.apps.publisher.tests import factories, JSON_CONTENT_TYPE
from course_discovery.apps.publisher.tests.utils import create_non_staff_user_and_login
from course_discovery.apps.publisher.utils import is_email_notification_enabled
from course_discovery.apps.publisher.views import CourseRunDetailView, logger as publisher_views_logger
from course_discovery.apps.publisher.utils import is_email_notification_enabled, get_internal_users
from course_discovery.apps.publisher.views import (
CourseRunDetailView, logger as publisher_views_logger, ROLE_WIDGET_HEADINGS
)
from course_discovery.apps.publisher.wrappers import CourseRunWrapper
from course_discovery.apps.publisher_comments.tests.factories import CommentFactory
......@@ -897,6 +903,52 @@ class CourseRunDetailTests(TestCase):
response = self.client.get(page_url)
self.assertEqual(response.status_code, 403)
def test_detail_page_with_role_assignment(self):
""" Verify that detail page contains role assignment data for internal user. """
# Add users in internal user group
pc_user = UserFactory()
marketing_user = UserFactory()
publisher_user = UserFactory()
internal_user_group = Group.objects.get(name=INTERNAL_USER_GROUP_NAME)
internal_user_group.user_set.add(*(self.user, pc_user, marketing_user, publisher_user))
assign_perm(Course.VIEW_PERMISSION, internal_user_group, self.course)
organization = OrganizationFactory()
self.course.organizations.add(organization)
# create three course user roles for internal users
for user, role in zip([pc_user, marketing_user, publisher_user], PublisherUserRole.choices):
factories.CourseUserRoleFactory(course=self.course, user=user, role=role)
response = self.client.get(self.page_url)
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)}
)
self.assertEqual(response.context['role_widgets'], expected_roles)
self.assertEqual(list(response.context['user_list']), list(get_internal_users()))
def test_detail_page_role_assignment_with_non_internal_user(self):
""" Verify that non internal user can't see change role assignment widget. """
# Create a non internal user and assign course view permission.
non_internal_user = UserFactory()
assign_perm(Course.VIEW_PERMISSION, non_internal_user, self.course)
self.client.logout()
self.client.login(username=non_internal_user.username, password=USER_PASSWORD)
response = self.client.get(self.page_url)
self.assertNotIn('role_widgets', response.context)
self.assertNotIn('user_list', response.context)
class ChangeStateViewTests(TestCase):
""" Tests for the `ChangeStateView`. """
......
"""
URLs for the course publisher views.
"""
from django.conf.urls import url
from django.conf.urls import url, include
from course_discovery.apps.publisher import views
urlpatterns = [
url(r'^api/', include('course_discovery.apps.publisher.api.urls', namespace='api')),
url(r'^dashboard/$', views.Dashboard.as_view(), name='publisher_dashboard'),
url(r'^courses/new$', views.CreateCourseView.as_view(), name='publisher_courses_new'),
url(r'^courses/(?P<pk>\d+)/view/$', views.ReadOnlyView.as_view(), name='publisher_courses_readonly'),
......
""" Publisher Utils."""
from course_discovery.apps.publisher.constants import ADMIN_GROUP_NAME
from course_discovery.apps.core.models import User
from course_discovery.apps.publisher.constants import ADMIN_GROUP_NAME, INTERNAL_USER_GROUP_NAME
def is_email_notification_enabled(user):
......@@ -27,3 +28,25 @@ def is_publisher_admin(user):
bool: True, if user is an administrator; otherwise, False.
"""
return user.groups.filter(name=ADMIN_GROUP_NAME).exists()
def is_internal_user(user):
""" Returns True if the user is an internal user.
Arguments:
user (:obj:`User`): User whose permissions should be checked.
Returns:
bool: True, if user is an internal user; otherwise, False.
"""
return user.groups.filter(name=INTERNAL_USER_GROUP_NAME).exists()
def get_internal_users():
"""
Returns a list of all internal users
Returns:
list
"""
return list(User.objects.filter(groups__name=INTERNAL_USER_GROUP_NAME))
......@@ -17,20 +17,31 @@ from django_fsm import TransitionNotAllowed
from guardian.shortcuts import get_objects_for_user
from rest_framework.generics import UpdateAPIView
from course_discovery.apps.publisher.choices import PublisherUserRole
from course_discovery.apps.publisher.forms import (
CourseForm, CourseRunForm, SeatForm, CustomCourseForm, CustomCourseRunForm, CustomSeatForm,
UpdateCourseForm)
CourseForm, CourseRunForm, SeatForm, CustomCourseForm, CustomCourseRunForm,
CustomSeatForm, UpdateCourseForm
)
from course_discovery.apps.publisher import mixins
from course_discovery.apps.publisher.models import Course, CourseRun, Seat, State, UserAttributes
from course_discovery.apps.publisher.models import (
Course, CourseRun, Seat, State, UserAttributes
)
from course_discovery.apps.publisher.serializers import UpdateCourseKeySerializer
from course_discovery.apps.publisher.utils import is_internal_user, get_internal_users
from course_discovery.apps.publisher.wrappers import CourseRunWrapper
logger = logging.getLogger(__name__)
SEATS_HIDDEN_FIELDS = ['price', 'currency', 'upgrade_deadline', 'credit_provider', 'credit_hours']
ROLE_WIDGET_HEADINGS = {
PublisherUserRole.PartnerCoordinator: _('PARTNER COORDINATOR'),
PublisherUserRole.MarketingReviewer: _('MARKETING'),
PublisherUserRole.Publisher: _('PUBLISHER'),
PublisherUserRole.CourseTeam: _('Course Team')
}
class Dashboard(mixins.LoginRequiredMixin, ListView):
""" Create Course View."""
......@@ -83,10 +94,32 @@ class CourseRunDetailView(mixins.LoginRequiredMixin, mixins.ViewPermissionMixin,
model = CourseRun
template_name = 'publisher/course_run_detail.html'
def get_role_widgets_data(self, course_roles):
""" Create role widgets list for course user roles. """
role_widgets = []
for course_role in course_roles:
role_widgets.append(
{
'user_course_role': course_role,
'heading': ROLE_WIDGET_HEADINGS.get(course_role.role)
}
)
return role_widgets
def get_context_data(self, **kwargs):
context = super(CourseRunDetailView, self).get_context_data(**kwargs)
context['object'] = CourseRunWrapper(context['object'])
context['comment_object'] = self.object.course
course_run = CourseRunWrapper(self.get_object())
context['object'] = course_run
context['comment_object'] = course_run.course
# Show role assignment widgets if user is an internal user.
if is_internal_user(self.request.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()
return context
def patch(self, *args, **kwargs):
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-12-01 20:12+0500\n"
"POT-Creation-Date: 2016-12-08 15:03+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"
......@@ -421,7 +421,7 @@ msgstr ""
msgid "Publisher"
msgstr ""
#: apps/publisher/choices.py
#: apps/publisher/choices.py apps/publisher/views.py
msgid "Course Team"
msgstr ""
......@@ -584,6 +584,18 @@ msgid "Course Role"
msgstr ""
#: apps/publisher/views.py
msgid "PARTNER COORDINATOR"
msgstr ""
#: apps/publisher/views.py
msgid "MARKETING"
msgstr ""
#: apps/publisher/views.py
msgid "PUBLISHER"
msgstr ""
#: apps/publisher/views.py
msgid "Course created successfully."
msgstr ""
......@@ -1223,23 +1235,22 @@ msgstr ""
msgid "All"
msgstr ""
#. Translators: Studio is an edX tool for course creation.
#: templates/publisher/course_run_detail.html
msgid "STUDIO"
msgstr ""
#. Translators: CAT is an acronym for Course Administration Tool.
#: templates/publisher/course_run_detail.html
msgid "CAT"
msgstr ""
#. Translators: DRUPAL is an edX marketing site.
#: templates/publisher/course_run_detail.html
msgid "DRUPAL"
msgstr ""
#: templates/publisher/course_run_detail.html
msgid "Salesforce"
msgstr ""
#: templates/publisher/course_run_detail.html
#: templates/publisher/course_run_form.html
#: templates/publisher/course_runs_list.html
msgid "Status"
......@@ -1384,8 +1395,16 @@ msgstr ""
msgid "Additional Notes"
msgstr ""
#: templates/publisher/course_run_detail/_all.html
msgid "Edit"
#: templates/publisher/course_run_detail/_approval_widget.html
msgid "EDIT"
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
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-12-01 20:12+0500\n"
"POT-Creation-Date: 2016-12-08 15:03+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-12-01 20:12+0500\n"
"POT-Creation-Date: 2016-12-08 15:03+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"
......@@ -525,7 +525,7 @@ msgstr "Märkétïng Révïéwér Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт α
msgid "Publisher"
msgstr "Püßlïshér Ⱡ'σяєм ιρѕυм ∂σł#"
#: apps/publisher/choices.py
#: apps/publisher/choices.py apps/publisher/views.py
msgid "Course Team"
msgstr "Çöürsé Téäm Ⱡ'σяєм ιρѕυм ∂σłσя #"
......@@ -704,6 +704,18 @@ msgid "Course Role"
msgstr "Çöürsé Rölé Ⱡ'σяєм ιρѕυм ∂σłσя #"
#: apps/publisher/views.py
msgid "PARTNER COORDINATOR"
msgstr "PÀRTNÉR ÇÖÖRDÌNÀTÖR Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт,#"
#: apps/publisher/views.py
msgid "MARKETING"
msgstr "MÀRKÉTÌNG Ⱡ'σяєм ιρѕυм ∂σł#"
#: apps/publisher/views.py
msgid "PUBLISHER"
msgstr "PÛBLÌSHÉR Ⱡ'σяєм ιρѕυм ∂σł#"
#: apps/publisher/views.py
msgid "Course created successfully."
msgstr "Çöürsé çréätéd süççéssfüllý. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢#"
......@@ -1486,23 +1498,22 @@ msgstr "Çöürsé Rün Détäïl Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт α
msgid "All"
msgstr "Àll Ⱡ'σяєм#"
#. Translators: Studio is an edX tool for course creation.
#: templates/publisher/course_run_detail.html
msgid "STUDIO"
msgstr "STÛDÌÖ Ⱡ'σяєм ιρѕυ#"
#. Translators: CAT is an acronym for Course Administration Tool.
#: templates/publisher/course_run_detail.html
msgid "CAT"
msgstr "ÇÀT Ⱡ'σяєм#"
#. Translators: DRUPAL is an edX marketing site.
#: templates/publisher/course_run_detail.html
msgid "DRUPAL"
msgstr "DRÛPÀL Ⱡ'σяєм ιρѕυ#"
#: templates/publisher/course_run_detail.html
msgid "Salesforce"
msgstr "Sälésförçé Ⱡ'σяєм ιρѕυм ∂σłσ#"
#: templates/publisher/course_run_detail.html
#: templates/publisher/course_run_form.html
#: templates/publisher/course_runs_list.html
msgid "Status"
......@@ -1654,9 +1665,17 @@ msgstr ""
msgid "Additional Notes"
msgstr "Àddïtïönäl Nötés Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αм#"
#: templates/publisher/course_run_detail/_all.html
msgid "Edit"
msgstr "Édït Ⱡ'σяєм ι#"
#: templates/publisher/course_run_detail/_approval_widget.html
msgid "EDIT"
msgstr "ÉDÌT Ⱡ'σяєм ι#"
#: templates/publisher/course_run_detail/_approval_widget.html
msgid "change owner"
msgstr "çhängé öwnér Ⱡ'σяєм ιρѕυм ∂σłσя ѕ#"
#: templates/publisher/course_run_detail/_approval_widget.html
msgid "CHANGE"
msgstr "ÇHÀNGÉ Ⱡ'σяєм ιρѕυ#"
#: templates/publisher/course_run_detail/_cat.html
msgid "Course Type"
......
......@@ -7,7 +7,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-12-01 20:12+0500\n"
"POT-Creation-Date: 2016-12-08 15:03+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"
......
$.ajaxSetup({
headers: {
'X-CSRFToken': Cookies.get('course_discovery_csrftoken')
}
});
$(document).ready(function() {
$('.btn-change-assignment').click(function(e){
var apiEndpoint = $(this).data('api-endpoint'),
roleName = $(this).data('role'),
$selectedOption = $('#selectUsers-' + roleName + ' option:selected'),
userId = $selectedOption.val(),
userName = $selectedOption.text();
e.preventDefault();
$.ajax({
url: apiEndpoint,
type: 'PATCH',
data: JSON.stringify({'user': userId}),
contentType: 'application/json',
success: function (response) {
$('#userFullName-' + roleName).text(userName);
$selectedOption.val(userId);
$('#userRoleContainer-' + roleName).show();
$('#changeRoleContainer-' + roleName).hide();
}
});
});
$('.change-role-assignment').click(function (e) {
var roleName = $(this).data('role');
e.preventDefault();
$('#changeRoleContainer-' + roleName).show();
$('#userRoleContainer-' + roleName).hide();
});
});
......@@ -17,9 +17,6 @@ $(document).ready(function() {
courseKeyValue = courseKeyInput.val().trim(),
courseTitleTag = $courseRunParentTag.find("#course-title").html().trim(),
startDateTag = $courseRunParentTag.find("#course-start").html().trim(),
headers = {
'X-CSRFToken': Cookies.get('course_discovery_csrftoken')
},
$studioInstanceSuccess = $(".studio-instance-success"),
successMessage = interpolateString(
gettext("You have successfully created a studio instance ({studioLinkTag}) for {courseRunDetail} with a start date of {startDate}"),
......@@ -36,7 +33,6 @@ $(document).ready(function() {
type: "PATCH",
data: JSON.stringify({lms_course_id: courseKeyValue}),
contentType: "application/json",
headers: headers,
success: function (response) {
data_table_studio.row($courseRunParentTag).remove().draw();
$("#studio-count").html(data_table_studio.rows().count());
......
......@@ -30,17 +30,13 @@ $(document).ready(function () {
$("#email-switch").change(function(e) {
var isEnabled = this.checked ? true : false,
switchLabel = gettext("OFF"),
headers = {
'X-CSRFToken': Cookies.get('course_discovery_csrftoken')
};
switchLabel = gettext("OFF");
e.preventDefault();
$.ajax({
url: "/publisher/user/toggle/email_settings/",
type: "POST",
data: {is_enabled: isEnabled},
headers: headers,
success: function (response) {
if (response.is_enabled) {
switchLabel = gettext("ON")
......
......@@ -637,3 +637,54 @@ select {
.hidden {
display: none;
}
.approval-widget {
.btn-course-edit {
@include padding(2px, 20px, 3px, 20px);
font-weight: 400;
font-size: 14px;
background: white;
border-radius: 5px;
&:hover, &:focus {
border-color: #065683;
background: #065683;
color: #f2f8fb;
}
}
.role-heading {
color: #999999;
}
.role-assignment-container {
.field-readonly {
@include margin-right(10px);
}
.change-role-container {
display: none;
.select-users-by-role {
@include margin-right(20px);
}
.btn-change-assignment {
@include padding(2px, 10px, 3px, 10px);
font-weight: 400;
font-size: 14px;
background: white;
border-radius: 5px;
&:hover, &:focus {
border-color: #065683;
background: #065683;
color: #f2f8fb;
}
}
}
}
}
......@@ -2,7 +2,7 @@
{% load comments %}
{% if user.is_authenticated and comment_object %}
<div class="comments-container">
<div>
<p>{% trans 'Add new comment' %}</p>
<div>
{% get_comment_form for comment_object as form %}
......
{% load i18n %}
{% load comments %}
{% if comment_object %}
<div class="comments-container">
<div>
{% get_comment_count for comment_object as comment_count %}
<h4 class="hd-4">
{% blocktrans with comment_count=comment_count trimmed %}
......
......@@ -57,6 +57,7 @@
<script src="{% static 'bower_components/moment/moment.js' %}"></script>
<script src="{% static 'bower_components/pikaday/pikaday.js' %}"></script>
<script src="{% static 'bower_components/datatables/media/js/jquery.dataTables.js' %}"></script>
<script src="{% static 'js/publisher/main.js' %}"></script>
<script src="{% static 'js/publisher/views/navbar.js' %}"></script>
<script src="{% static 'js/publisher/utils.js' %}"></script>
{% endcompress %}
......
......@@ -9,64 +9,67 @@
{% endblock title %}
{% block page_content %}
<div class="publisher-container course-detail">
<nav class="administration-nav">
<div class="tab-container">
<button class="selected" data-tab="#tab-1">{% trans "All" %}</button>
<button data-tab="#tab-2">{% trans "STUDIO" %}</button>
<button data-tab="#tab-3">{% trans "CAT" %}</button>
<button data-tab="#tab-4">{% trans "DRUPAL" %}</button>
<button data-tab="#tab-5">{% trans "Salesforce" %}</button>
</div>
</nav>
<div class="layout-1t2t layout-flush publisher-container course-detail">
<main class="layout-col layout-col-b">
<nav class="administration-nav">
<div class="tab-container">
<button class="selected" data-tab="#tab-1">{% trans "All" %}</button>
{% comment %}Translators: Studio is an edX tool for course creation.{% endcomment %}
<button data-tab="#tab-2">{% trans "STUDIO" %}</button>
{% comment %}Translators: CAT is an acronym for Course Administration Tool.{% endcomment %}
<button data-tab="#tab-3">{% trans "CAT" %}</button>
{% comment %}Translators: DRUPAL is an edX marketing site.{% endcomment %}
<button data-tab="#tab-4">{% trans "DRUPAL" %}</button>
<button data-tab="#tab-5">Salesforce</button>
</div>
</nav>
<div id="app">
<div class="page-header">
<h2 class="hd-2 emphasized">
<span class="course-name">{{ object.title }}</span>
</h2>
</div>
<div id="app">
<div class="page-header">
<h2 class="hd-2 emphasized">
<span class="course-name">{{ object.title }}</span>
</h2>
</div>
{% include 'alert_messages.html' %}
{% include 'alert_messages.html' %}
<div class="status-information">
<div class="info-item">
<span class="item">
<span class="heading">{% trans "Status" %}:</span>
<span>{{ object.workflow_state }}</span>
</span>
<div class="status-information">
<div class="info-item">
<span class="item">
<span class="heading">{% trans "Status" %}:</span>
<span>{{ object.workflow_state }}</span>
</span>
</div>
</div>
</div>
<div class="tabs">
<div id="tab-1" class="tab-content active">
{% include 'publisher/course_run_detail/_all.html' %}
</div>
<div id="tab-2" class="tab-content">
{% include 'publisher/course_run_detail/_studio.html' %}
<div class="tabs">
<div id="tab-1" class="tab-content active">
{% include 'publisher/course_run_detail/_all.html' %}
</div>
<div id="tab-2" class="tab-content">
{% include 'publisher/course_run_detail/_studio.html' %}
</div>
<div id="tab-3" class="tab-content">
{% include 'publisher/course_run_detail/_cat.html' %}
</div>
<div id="tab-4" class="tab-content">
{% include 'publisher/course_run_detail/_drupal.html' %}
</div>
<div id="tab-5" class="tab-content">
{% include 'publisher/course_run_detail/_salesforce.html' %}
</div>
</div>
<div id="tab-3" class="tab-content">
{% include 'publisher/course_run_detail/_cat.html' %}
</div>
<div id="tab-4" class="tab-content">
{% include 'publisher/course_run_detail/_drupal.html' %}
</div>
<div id="tab-5" class="tab-content">
{% include 'publisher/course_run_detail/_salesforce.html' %}
</div>
</div>
<div class="actions">
<form action="{% url 'publisher:publisher_change_state' course_run_id=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>
</main>
<aside class="layout-col layout-col-a">
{% include 'publisher/course_run_detail/_approval_widget.html' %}
</aside>
</div>
{% endblock %}
{% block extra_js %}
<script src="{% static 'bower_components/clipboard/dist/clipboard.min.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/comments.js' %}"></script>
<script>
......
......@@ -256,12 +256,4 @@
<div class="copy">{{ object.notes }}</div>
</div>
</div>
<div class="comment-container">
<a href="{% url 'publisher:publisher_course_runs_edit' pk=object.id %}" class="btn btn-neutral btn-add">
<span class="icon fa fa-edit" aria-hidden="true"></span>&nbsp;&nbsp;{% trans "Edit" %}
</a>
{% include 'comments/comments_list.html' %}
{% include 'comments/add_auth_comments.html' %}
</div>
<div class="clearfix"></div>
{% load i18n %}
<div class="approval-widget">
<a href="{% url 'publisher:publisher_course_runs_edit' pk=object.id %}" class="btn btn-neutral btn-course-edit">{% trans "EDIT" %}</a>
{% for role_widget in role_widgets %}
<div class="role-widget">
<hr>
<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">
{{ role_widget.user_course_role.user.full_name }}
</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>
{% endfor %}
<hr>
<div class="actions">
<form action="{% url 'publisher:publisher_change_state' course_run_id=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 class="comment-container">
{% include 'comments/comments_list.html' %}
{% include 'comments/add_auth_comments.html' %}
</div>
</div>
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