Commit d112c0b8 by jagonzalr Committed by Zia Fazal

show button new library in studio depending on flags and user staff status

add flag DISABLE_LIBRARY_CREATION

add comma

use CourseCreatorRole to determine if user can create a library

add disable library creation feature flag

Conflicts:
	cms/djangoapps/contentstore/views/course.py

ENABLE_CONTENT_LIBRARIES flag

check for course creator role for library creation

Conflicts:
	cms/djangoapps/contentstore/views/course.py

add unit tests

make check of creation of library a true/false for forntend, add security in api call, clean tests

update tests

fix docstring of tests

fixed quality violation

fixed broken unit test and quality violations

Feedback changes and unit test to assert libraries are visible to non staff users too

fixed quality violation and feedback changes
parent 2adb03ec
...@@ -11,7 +11,7 @@ import ddt ...@@ -11,7 +11,7 @@ import ddt
from mock import patch from mock import patch
from student.auth import has_studio_read_access, has_studio_write_access from student.auth import has_studio_read_access, has_studio_write_access
from student.roles import ( from student.roles import (
CourseInstructorRole, CourseStaffRole, CourseCreatorRole, LibraryUserRole, CourseInstructorRole, CourseStaffRole, LibraryUserRole,
OrgStaffRole, OrgInstructorRole, OrgLibraryUserRole, OrgStaffRole, OrgInstructorRole, OrgLibraryUserRole,
) )
from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore import ModuleStoreEnum
...@@ -25,6 +25,7 @@ from xblock_django.user_service import DjangoXBlockUserService ...@@ -25,6 +25,7 @@ from xblock_django.user_service import DjangoXBlockUserService
from xmodule.x_module import STUDIO_VIEW from xmodule.x_module import STUDIO_VIEW
from student import auth from student import auth
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from course_creators.views import add_user_with_status_granted
class LibraryTestCase(ModuleStoreTestCase): class LibraryTestCase(ModuleStoreTestCase):
...@@ -533,9 +534,13 @@ class TestLibraryAccess(SignalDisconnectTestMixin, LibraryTestCase): ...@@ -533,9 +534,13 @@ class TestLibraryAccess(SignalDisconnectTestMixin, LibraryTestCase):
self.client.logout() self.client.logout()
self._assert_cannot_create_library(expected_code=302) # 302 redirect to login expected self._assert_cannot_create_library(expected_code=302) # 302 redirect to login expected
# Now check that logged-in users without CourseCreator role can still create libraries # Now check that logged-in users without CourseCreator role cannot create libraries
self._login_as_non_staff_user(logout_first=False) self._login_as_non_staff_user(logout_first=False)
self.assertFalse(CourseCreatorRole().has_user(self.non_staff_user)) with patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREATOR_GROUP': True}):
self._assert_cannot_create_library(expected_code=403) # 403 user is not CourseCreator
# Now check that logged-in users with CourseCreator role can create libraries
add_user_with_status_granted(self.user, self.non_staff_user)
with patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREATOR_GROUP': True}): with patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREATOR_GROUP': True}):
lib_key2 = self._create_library(library="lib2", display_name="Test Library 2") lib_key2 = self._create_library(library="lib2", display_name="Test Library 2")
library2 = modulestore().get_library(lib_key2) library2 = modulestore().get_library(lib_key2)
......
...@@ -26,7 +26,7 @@ from .component import ( ...@@ -26,7 +26,7 @@ from .component import (
ADVANCED_COMPONENT_TYPES, ADVANCED_COMPONENT_TYPES,
) )
from .item import create_xblock_info from .item import create_xblock_info
from .library import LIBRARIES_ENABLED from .library import LIBRARIES_ENABLED, get_library_creator_status
from ccx_keys.locator import CCXLocator from ccx_keys.locator import CCXLocator
from contentstore.course_group_config import ( from contentstore.course_group_config import (
COHORT_SCHEME, COHORT_SCHEME,
...@@ -465,6 +465,7 @@ def course_listing(request): ...@@ -465,6 +465,7 @@ def course_listing(request):
List all courses available to the logged in user List all courses available to the logged in user
""" """
courses, in_process_course_actions = get_courses_accessible_to_user(request) courses, in_process_course_actions = get_courses_accessible_to_user(request)
user = request.user
libraries = _accessible_libraries_list(request.user) if LIBRARIES_ENABLED else [] libraries = _accessible_libraries_list(request.user) if LIBRARIES_ENABLED else []
def format_in_process_course_view(uca): def format_in_process_course_view(uca):
...@@ -509,11 +510,11 @@ def course_listing(request): ...@@ -509,11 +510,11 @@ def course_listing(request):
'in_process_course_actions': in_process_course_actions, 'in_process_course_actions': in_process_course_actions,
'libraries_enabled': LIBRARIES_ENABLED, 'libraries_enabled': LIBRARIES_ENABLED,
'libraries': [format_library_for_view(lib) for lib in libraries], 'libraries': [format_library_for_view(lib) for lib in libraries],
'show_new_library_button': LIBRARIES_ENABLED and request.user.is_active, 'show_new_library_button': get_library_creator_status(user),
'user': request.user, 'user': user,
'request_course_creator_url': reverse('contentstore.views.request_course_creator'), 'request_course_creator_url': reverse('contentstore.views.request_course_creator'),
'course_creator_status': _get_course_creator_status(request.user), 'course_creator_status': _get_course_creator_status(user),
'rerun_creator_status': GlobalStaff().has_user(request.user), 'rerun_creator_status': GlobalStaff().has_user(user),
'allow_unicode_course_id': settings.FEATURES.get('ALLOW_UNICODE_COURSE_ID', False), 'allow_unicode_course_id': settings.FEATURES.get('ALLOW_UNICODE_COURSE_ID', False),
'allow_course_reruns': settings.FEATURES.get('ALLOW_COURSE_RERUNS', True), 'allow_course_reruns': settings.FEATURES.get('ALLOW_COURSE_RERUNS', True),
}) })
...@@ -1619,6 +1620,7 @@ def _get_course_creator_status(user): ...@@ -1619,6 +1620,7 @@ def _get_course_creator_status(user):
If the user passed in has not previously visited the index page, it will be If the user passed in has not previously visited the index page, it will be
added with status 'unrequested' if the course creator group is in use. added with status 'unrequested' if the course creator group is in use.
""" """
if user.is_staff: if user.is_staff:
course_creator_status = 'granted' course_creator_status = 'granted'
elif settings.FEATURES.get('DISABLE_COURSE_CREATION', False): elif settings.FEATURES.get('DISABLE_COURSE_CREATION', False):
......
...@@ -9,7 +9,7 @@ import logging ...@@ -9,7 +9,7 @@ import logging
from contentstore.views.item import create_xblock_info from contentstore.views.item import create_xblock_info
from contentstore.utils import reverse_library_url, add_instructor from contentstore.utils import reverse_library_url, add_instructor
from django.http import HttpResponseNotAllowed, Http404 from django.http import HttpResponseNotAllowed, Http404, HttpResponseForbidden
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.conf import settings from django.conf import settings
...@@ -25,6 +25,7 @@ from xmodule.modulestore import ModuleStoreEnum ...@@ -25,6 +25,7 @@ from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from .user import user_with_role from .user import user_with_role
from course_creators.views import get_course_creator_status
from .component import get_component_templates, CONTAINER_TEMPLATES from .component import get_component_templates, CONTAINER_TEMPLATES
from student.auth import ( from student.auth import (
STUDIO_VIEW_USERS, STUDIO_EDIT_ROLES, get_user_permissions, has_studio_read_access, has_studio_write_access STUDIO_VIEW_USERS, STUDIO_EDIT_ROLES, get_user_permissions, has_studio_read_access, has_studio_write_access
...@@ -39,6 +40,22 @@ log = logging.getLogger(__name__) ...@@ -39,6 +40,22 @@ log = logging.getLogger(__name__)
LIBRARIES_ENABLED = settings.FEATURES.get('ENABLE_CONTENT_LIBRARIES', False) LIBRARIES_ENABLED = settings.FEATURES.get('ENABLE_CONTENT_LIBRARIES', False)
def get_library_creator_status(user):
"""
Helper method for returning the library creation status for a particular user,
taking into account the value LIBRARIES_ENABLED.
"""
if not LIBRARIES_ENABLED:
return False
elif user.is_staff:
return True
elif settings.FEATURES.get('ENABLE_CREATOR_GROUP', False):
return get_course_creator_status(user) == 'granted'
else:
return True
@login_required @login_required
@ensure_csrf_cookie @ensure_csrf_cookie
@require_http_methods(('GET', 'POST')) @require_http_methods(('GET', 'POST'))
...@@ -50,6 +67,10 @@ def library_handler(request, library_key_string=None): ...@@ -50,6 +67,10 @@ def library_handler(request, library_key_string=None):
log.exception("Attempted to use the content library API when the libraries feature is disabled.") log.exception("Attempted to use the content library API when the libraries feature is disabled.")
raise Http404 # Should never happen because we test the feature in urls.py also raise Http404 # Should never happen because we test the feature in urls.py also
if not get_library_creator_status(request.user):
if not request.user.is_staff:
return HttpResponseForbidden()
if library_key_string is not None and request.method == 'POST': if library_key_string is not None and request.method == 'POST':
return HttpResponseNotAllowed(("POST",)) return HttpResponseNotAllowed(("POST",))
......
...@@ -24,6 +24,7 @@ from course_action_state.models import CourseRerunState ...@@ -24,6 +24,7 @@ from course_action_state.models import CourseRerunState
from opaque_keys.edx.locator import CourseLocator from opaque_keys.edx.locator import CourseLocator
from search.api import perform_search from search.api import perform_search
from student.auth import has_course_author_access from student.auth import has_course_author_access
from student.roles import LibraryUserRole
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from util.date_utils import get_default_time_display from util.date_utils import get_default_time_display
from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore import ModuleStoreEnum
...@@ -75,23 +76,38 @@ class TestCourseIndex(CourseTestCase): ...@@ -75,23 +76,38 @@ class TestCourseIndex(CourseTestCase):
""" """
Test getting the list of libraries from the course listing page Test getting the list of libraries from the course listing page
""" """
# Add a library: def _assert_library_link_present(response, library):
lib1 = LibraryFactory.create() """
Asserts there's a valid library link on libraries tab.
index_url = '/home/' """
index_response = self.client.get(index_url, {}, HTTP_ACCEPT='text/html') parsed_html = lxml.html.fromstring(response.content)
parsed_html = lxml.html.fromstring(index_response.content)
library_link_elements = parsed_html.find_class('library-link') library_link_elements = parsed_html.find_class('library-link')
self.assertEqual(len(library_link_elements), 1) self.assertEqual(len(library_link_elements), 1)
link = library_link_elements[0] link = library_link_elements[0]
self.assertEqual( self.assertEqual(
link.get("href"), link.get("href"),
reverse_library_url('library_handler', lib1.location.library_key), reverse_library_url('library_handler', library.location.library_key),
) )
# now test that url # now test that url
outline_response = self.client.get(link.get("href"), {}, HTTP_ACCEPT='text/html') outline_response = self.client.get(link.get("href"), {}, HTTP_ACCEPT='text/html')
self.assertEqual(outline_response.status_code, 200) self.assertEqual(outline_response.status_code, 200)
# Add a library:
lib1 = LibraryFactory.create()
index_url = '/home/'
index_response = self.client.get(index_url, {}, HTTP_ACCEPT='text/html')
_assert_library_link_present(index_response, lib1)
# Make sure libraries are visible to non-staff users too
self.client.logout()
non_staff_user, non_staff_userpassword = self.create_non_staff_user()
lib2 = LibraryFactory.create(user_id=non_staff_user.id)
LibraryUserRole(lib2.location.library_key).add_users(non_staff_user)
self.client.login(username=non_staff_user.username, password=non_staff_userpassword)
index_response = self.client.get(index_url, {}, HTTP_ACCEPT='text/html')
_assert_library_link_present(index_response, lib2)
def test_is_staff_access(self): def test_is_staff_access(self):
""" """
Test that people with is_staff see the courses and can navigate into them Test that people with is_staff see the courses and can navigate into them
......
...@@ -5,12 +5,15 @@ More important high-level tests are in contentstore/tests/test_libraries.py ...@@ -5,12 +5,15 @@ More important high-level tests are in contentstore/tests/test_libraries.py
""" """
from contentstore.tests.utils import AjaxEnabledTestClient, parse_json from contentstore.tests.utils import AjaxEnabledTestClient, parse_json
from contentstore.utils import reverse_course_url, reverse_library_url from contentstore.utils import reverse_course_url, reverse_library_url
from contentstore.tests.utils import CourseTestCase
from contentstore.views.component import get_component_templates from contentstore.views.component import get_component_templates
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from contentstore.views.library import get_library_creator_status
from course_creators.views import add_user_with_status_granted as grant_course_creator_status
from xmodule.modulestore.tests.factories import LibraryFactory from xmodule.modulestore.tests.factories import LibraryFactory
from mock import patch from mock import patch
from opaque_keys.edx.locator import CourseKey, LibraryLocator from opaque_keys.edx.locator import CourseKey, LibraryLocator
import ddt import ddt
import mock
from student.roles import LibraryUserRole from student.roles import LibraryUserRole
LIBRARY_REST_URL = '/library/' # URL for GET/POST requests involving libraries LIBRARY_REST_URL = '/library/' # URL for GET/POST requests involving libraries
...@@ -24,7 +27,7 @@ def make_url_for_lib(key): ...@@ -24,7 +27,7 @@ def make_url_for_lib(key):
@ddt.ddt @ddt.ddt
class UnitTestLibraries(ModuleStoreTestCase): class UnitTestLibraries(CourseTestCase):
""" """
Unit tests for library views Unit tests for library views
""" """
...@@ -38,6 +41,27 @@ class UnitTestLibraries(ModuleStoreTestCase): ...@@ -38,6 +41,27 @@ class UnitTestLibraries(ModuleStoreTestCase):
###################################################### ######################################################
# Tests for /library/ - list and create libraries: # Tests for /library/ - list and create libraries:
@mock.patch("contentstore.views.library.LIBRARIES_ENABLED", False)
def test_library_creator_status_libraries_not_enabled(self):
_, nostaff_user = self.create_non_staff_authed_user_client()
self.assertEqual(get_library_creator_status(nostaff_user), False)
@mock.patch("contentstore.views.library.LIBRARIES_ENABLED", True)
def test_library_creator_status_with_is_staff_user(self):
self.assertEqual(get_library_creator_status(self.user), True)
@mock.patch("contentstore.views.library.LIBRARIES_ENABLED", True)
def test_library_creator_status_with_course_creator_role(self):
_, nostaff_user = self.create_non_staff_authed_user_client()
with mock.patch.dict('django.conf.settings.FEATURES', {"ENABLE_CREATOR_GROUP": True}):
grant_course_creator_status(self.user, nostaff_user)
self.assertEqual(get_library_creator_status(nostaff_user), True)
@mock.patch("contentstore.views.library.LIBRARIES_ENABLED", True)
def test_library_creator_status_with_no_course_creator_role(self):
_, nostaff_user = self.create_non_staff_authed_user_client()
self.assertEqual(get_library_creator_status(nostaff_user), True)
@patch("contentstore.views.library.LIBRARIES_ENABLED", False) @patch("contentstore.views.library.LIBRARIES_ENABLED", False)
def test_with_libraries_disabled(self): def test_with_libraries_disabled(self):
""" """
...@@ -87,18 +111,45 @@ class UnitTestLibraries(ModuleStoreTestCase): ...@@ -87,18 +111,45 @@ class UnitTestLibraries(ModuleStoreTestCase):
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREATOR_GROUP': True}) @patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREATOR_GROUP': True})
def test_lib_create_permission(self): def test_lib_create_permission(self):
""" """
Users who are not given course creator roles should still be able to Users who are given course creator roles should be able to create libraries.
create libraries.
""" """
self.client.logout() self.client.logout()
ns_user, password = self.create_non_staff_user() ns_user, password = self.create_non_staff_user()
self.client.login(username=ns_user.username, password=password) self.client.login(username=ns_user.username, password=password)
grant_course_creator_status(self.user, ns_user)
response = self.client.ajax_post(LIBRARY_REST_URL, {
'org': 'org', 'library': 'lib', 'display_name': "New Library",
})
self.assertEqual(response.status_code, 200)
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREATOR_GROUP': False})
def test_lib_create_permission_no_course_creator_role_and_no_course_creator_group(self):
"""
Users who are not given course creator roles should still be able to create libraries
if ENABLE_CREATOR_GROUP is not enabled.
"""
self.client.logout()
ns_user, password = self.create_non_staff_user()
self.client.login(username=ns_user.username, password=password)
response = self.client.ajax_post(LIBRARY_REST_URL, { response = self.client.ajax_post(LIBRARY_REST_URL, {
'org': 'org', 'library': 'lib', 'display_name': "New Library", 'org': 'org', 'library': 'lib', 'display_name': "New Library",
}) })
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@patch.dict('django.conf.settings.FEATURES', {'ENABLE_CREATOR_GROUP': True})
def test_lib_create_permission_no_course_creator_role_and_course_creator_group(self):
"""
Users who are not given course creator roles should not be able to create libraries
if ENABLE_CREATOR_GROUP is enabled.
"""
self.client.logout()
ns_user, password = self.create_non_staff_user()
self.client.login(username=ns_user.username, password=password)
response = self.client.ajax_post(LIBRARY_REST_URL, {
'org': 'org', 'library': 'lib', 'display_name': "New Library",
})
self.assertEqual(response.status_code, 403)
@ddt.data( @ddt.data(
{}, {},
{'org': 'org'}, {'org': 'org'},
......
...@@ -33,7 +33,6 @@ from openedx.core.djangolib.markup import HTML, Text ...@@ -33,7 +33,6 @@ from openedx.core.djangolib.markup import HTML, Text
% elif course_creator_status=='disallowed_for_this_site' and settings.FEATURES.get('STUDIO_REQUEST_EMAIL',''): % elif course_creator_status=='disallowed_for_this_site' and settings.FEATURES.get('STUDIO_REQUEST_EMAIL',''):
<a href="mailto:${settings.FEATURES.get('STUDIO_REQUEST_EMAIL','')}">${_("Email staff to create course")}</a> <a href="mailto:${settings.FEATURES.get('STUDIO_REQUEST_EMAIL','')}">${_("Email staff to create course")}</a>
% endif % endif
% if show_new_library_button: % if show_new_library_button:
<a href="#" class="button new-button new-library-button"><span class="icon fa fa-plus icon-inline" aria-hidden="true"></span> <a href="#" class="button new-button new-library-button"><span class="icon fa fa-plus icon-inline" aria-hidden="true"></span>
${_("New Library")}</a> ${_("New Library")}</a>
......
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