Merge branch 'release'

Conflicts:
	common/djangoapps/course_modes/views.py
	common/djangoapps/student/tests/test_roles.py
	common/djangoapps/student/views.py
	common/lib/opaque_keys/opaque_keys/__init__.py
	common/lib/opaque_keys/opaque_keys/tests/test_opaque_keys.py
	common/lib/xmodule/xmodule/contentstore/mongo.py
	lms/djangoapps/certificates/management/commands/gen_cert_report.py
	lms/djangoapps/notes/views.py
This commit is contained in:
Sarina Canelake
2014-06-10 16:59:23 -04:00
31 changed files with 271 additions and 117 deletions

View File

@@ -171,7 +171,7 @@ def remove_transcripts_from_store(_step, subs_id):
)
try:
content = contentstore().find(content_location)
contentstore().delete(content.get_id())
contentstore().delete(content.location)
print('Transcript file was removed from store.')
except NotFoundError:
print('Transcript file was NOT found and not removed.')

View File

@@ -6,14 +6,15 @@ import random
from chrono import Timer
from mock import patch, Mock
import ddt
from django.test import RequestFactory
from contentstore.views.course import _accessible_courses_list, _accessible_courses_list_from_groups
from contentstore.views.course import _accessible_courses_list, _accessible_courses_list_from_groups, AccessListFallback
from contentstore.utils import delete_course_and_groups, reverse_course_url
from contentstore.tests.utils import AjaxEnabledTestClient
from student.tests.factories import UserFactory
from student.roles import CourseInstructorRole, CourseStaffRole, GlobalStaff
from student.roles import CourseInstructorRole, CourseStaffRole, GlobalStaff, OrgStaffRole, OrgInstructorRole
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory
from opaque_keys.edx.locations import SlashSeparatedCourseKey
@@ -24,6 +25,7 @@ TOTAL_COURSES_COUNT = 500
USER_COURSES_COUNT = 50
@ddt.ddt
class TestCourseListing(ModuleStoreTestCase):
"""
Unit tests for getting the list of courses for a logged in user
@@ -263,7 +265,7 @@ class TestCourseListing(ModuleStoreTestCase):
course_db_record = modulestore()._find_one(course.location)
course_db_record.setdefault('metadata', {}).get('tabs', []).append({"type": "wiko", "name": "Wiki" })
modulestore().collection.update(
{'_id': course_db_record['_id']},
{'_id': course.location.to_deprecated_son()},
{'$set': {
'metadata.tabs': course_db_record['metadata']['tabs'],
}},
@@ -271,3 +273,31 @@ class TestCourseListing(ModuleStoreTestCase):
courses_list = _accessible_courses_list_from_groups(self.request)
self.assertEqual(len(courses_list), 1, courses_list)
@ddt.data(OrgStaffRole('AwesomeOrg'), OrgInstructorRole('AwesomeOrg'))
def test_course_listing_org_permissions(self, role):
"""
Create multiple courses within the same org. Verify that someone with org-wide permissions can access
all of them.
"""
org_course_one = SlashSeparatedCourseKey('AwesomeOrg', 'Course1', 'RunBabyRun')
CourseFactory.create(
org=org_course_one.org,
number=org_course_one.course,
run=org_course_one.run
)
org_course_two = SlashSeparatedCourseKey('AwesomeOrg', 'Course2', 'RunRunRun')
CourseFactory.create(
org=org_course_two.org,
number=org_course_two.course,
run=org_course_two.run
)
# Two types of org-wide roles have edit permissions: staff and instructor. We test both
role.add_users(self.user)
with self.assertRaises(AccessListFallback):
_accessible_courses_list_from_groups(self.request)
courses_list = _accessible_courses_list(self.request)
self.assertEqual(len(courses_list), 2)

View File

@@ -11,7 +11,7 @@ from contentstore.tests.modulestore_config import TEST_MODULESTORE
from contentstore.tests.utils import AjaxEnabledTestClient
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from contentstore.utils import reverse_url, reverse_course_url
from student.roles import CourseInstructorRole, CourseStaffRole
from student.roles import CourseInstructorRole, CourseStaffRole, OrgStaffRole, OrgInstructorRole
from contentstore.views.access import has_course_access
from student import auth
@@ -95,10 +95,13 @@ class TestCourseAccess(ModuleStoreTestCase):
# doesn't use role.users_with_role b/c it's verifying the roles.py behavior
user_by_role = {}
# add the misc users to the course in different groups
for role in [CourseInstructorRole, CourseStaffRole]:
for role in [CourseInstructorRole, CourseStaffRole, OrgStaffRole, OrgInstructorRole]:
user_by_role[role] = []
# pylint: disable=protected-access
group = role(self.course_key)
# Org-based roles are created via org name, rather than course_key
if (role is OrgStaffRole) or (role is OrgInstructorRole):
group = role(self.course_key.org)
else:
group = role(self.course_key)
# NOTE: this loop breaks the roles.py abstraction by purposely assigning
# users to one of each possible groupname in order to test that has_course_access
# and remove_user work
@@ -109,21 +112,28 @@ class TestCourseAccess(ModuleStoreTestCase):
course_team_url = reverse_course_url('course_team_handler', self.course_key)
response = self.client.get_html(course_team_url)
for role in [CourseInstructorRole, CourseStaffRole]:
for role in [CourseInstructorRole, CourseStaffRole]: # Global and org-based roles don't appear on this page
for user in user_by_role[role]:
self.assertContains(response, user.email)
# test copying course permissions
copy_course_key = SlashSeparatedCourseKey('copyu', 'copydept.mycourse', 'myrun')
for role in [CourseInstructorRole, CourseStaffRole]:
auth.add_users(
self.user,
role(copy_course_key),
*role(self.course_key).users_with_role()
)
for role in [CourseInstructorRole, CourseStaffRole, OrgStaffRole, OrgInstructorRole]:
if (role is OrgStaffRole) or (role is OrgInstructorRole):
auth.add_users(
self.user,
role(copy_course_key.org),
*role(self.course_key.org).users_with_role()
)
else:
auth.add_users(
self.user,
role(copy_course_key),
*role(self.course_key).users_with_role()
)
# verify access in copy course and verify that removal from source course w/ the various
# groupnames works
for role in [CourseInstructorRole, CourseStaffRole]:
for role in [CourseInstructorRole, CourseStaffRole, OrgStaffRole, OrgInstructorRole]:
for user in user_by_role[role]:
# forcefully decache the groups: premise is that any real request will not have
# multiple objects repr the same user but this test somehow uses different instance
@@ -132,5 +142,8 @@ class TestCourseAccess(ModuleStoreTestCase):
del user._roles
self.assertTrue(has_course_access(user, copy_course_key), "{} no copy access".format(user))
auth.remove_users(self.user, role(self.course_key), user)
if (role is OrgStaffRole) or (role is OrgInstructorRole):
auth.remove_users(self.user, role(self.course_key.org), user)
else:
auth.remove_users(self.user, role(self.course_key), user)
self.assertFalse(has_course_access(user, self.course_key), "{} remove didn't work".format(user))

View File

@@ -88,7 +88,7 @@ class TestSaveSubsToStore(ModuleStoreTestCase):
"""Remove, if subtitles content exists."""
try:
content = contentstore().find(self.content_location)
contentstore().delete(content.get_id())
contentstore().delete(content.location)
except NotFoundError:
pass
@@ -171,7 +171,7 @@ class TestDownloadYoutubeSubs(ModuleStoreTestCase):
content_location = StaticContent.compute_location(self.course.id, filename)
try:
content = contentstore().find(content_location)
contentstore().delete(content.get_id())
contentstore().delete(content.location)
except NotFoundError:
pass

View File

@@ -1,4 +1,6 @@
from student.roles import CourseStaffRole, GlobalStaff, CourseInstructorRole
""" Helper methods for determining user access permissions in Studio """
from student.roles import CourseStaffRole, GlobalStaff, CourseInstructorRole, OrgStaffRole, OrgInstructorRole
from student import auth
@@ -14,6 +16,10 @@ def has_course_access(user, course_key, role=CourseStaffRole):
"""
if GlobalStaff().has_user(user):
return True
if OrgInstructorRole(org=course_key.org).has_user(user):
return True
if OrgStaffRole(org=course_key.org).has_user(user):
return True
return auth.has_access(user, role(course_key))

View File

@@ -68,6 +68,14 @@ __all__ = ['course_info_handler', 'course_handler', 'course_info_update_handler'
'textbooks_list_handler', 'textbooks_detail_handler']
class AccessListFallback(Exception):
"""
An exception that is raised whenever we need to `fall back` to fetching *all* courses
available to a user, rather than using a shorter method (i.e. fetching by group)
"""
pass
def _get_course_module(course_key, user, depth=0):
"""
Internal method used to calculate and return the locator and course module
@@ -190,11 +198,17 @@ def _accessible_courses_list_from_groups(request):
for course_access in all_courses:
course_key = course_access.course_id
if course_key not in courses_list:
if course_key is None:
# If the course_access does not have a course_id, it's an org-based role, so we fall back
raise AccessListFallback
try:
course = modulestore('direct').get_course(course_key)
if course is not None and not isinstance(course, ErrorDescriptor):
# ignore deleted or errored courses
courses_list[course_key] = course
except ItemNotFoundError:
# If a user has access to a course that doesn't exist, don't do anything with that course
pass
if course is not None and not isinstance(course, ErrorDescriptor):
# ignore deleted or errored courses
courses_list[course_key] = course
return courses_list.values()
@@ -213,7 +227,7 @@ def course_listing(request):
else:
try:
courses = _accessible_courses_list_from_groups(request)
except ItemNotFoundError:
except AccessListFallback:
# user have some old groups or there was some error getting courses from django groups
# so fallback to iterating through all courses
courses = _accessible_courses_list(request)