Files
edx-platform/lms/djangoapps/course_api/api.py
Dillon Dumesnil 78a1b835fa Update the LMS courses API for staff permissions
We want to allow staff to see all courses in the LMS.
This changes the behavior from staff being treated like
an AnonymousUser (unless an username query parameter is
provided) to being treated like staff.

I also added in some tests for the other paths in this
function that did not seem to be tested.
2020-04-01 13:45:37 -07:00

247 lines
8.1 KiB
Python

"""
Course API
"""
import logging
from edx_when.api import get_dates_for_course
from django.conf import settings
from django.contrib.auth.models import AnonymousUser, User
from django.urls import reverse
from rest_framework.exceptions import PermissionDenied
import search
import six
from lms.djangoapps.courseware.access import has_access
from lms.djangoapps.courseware.courses import (
get_course_overview_with_access,
get_courses,
get_permission_for_course_about
)
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
from openedx.core.lib.api.view_utils import LazySequence
from student.roles import GlobalStaff
from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError
from .permissions import can_view_courses_for_username
logger = logging.getLogger(__name__) # pylint: disable=invalid-name
UNKNOWN_BLOCK_DISPLAY_NAME = 'UNKNOWN'
def get_effective_user(requesting_user, target_username):
"""
Get the user we want to view information on behalf of.
"""
if target_username == requesting_user.username:
return requesting_user
# This is the default behavior if username is not specified as a query parameter
# which is why the is_staff check is happening inside of here.
elif target_username == '':
if requesting_user.is_staff:
return requesting_user
return AnonymousUser()
elif target_username and can_view_courses_for_username(requesting_user, target_username):
return User.objects.get(username=target_username)
else:
raise PermissionDenied()
def course_detail(request, username, course_key):
"""
Return a single course identified by `course_key`.
The course must be visible to the user identified by `username` and the
logged-in user should have permission to view courses available to that
user.
Arguments:
request (HTTPRequest):
Used to identify the logged-in user and to instantiate the course
module to retrieve the course about description
username (string):
The name of the user `requesting_user would like to be identified as.
course_key (CourseKey): Identifies the course of interest
Return value:
`CourseOverview` object representing the requested course
"""
user = get_effective_user(request.user, username)
overview = get_course_overview_with_access(
user,
get_permission_for_course_about(),
course_key,
)
overview.effective_user = user
return overview
def _filter_by_search(course_queryset, search_term):
"""
Filters a course queryset by the specified search term.
"""
if not settings.FEATURES['ENABLE_COURSEWARE_SEARCH'] or not search_term:
return course_queryset
# Return all the results, 10K is the maximum allowed value for ElasticSearch.
# We should use 0 after upgrading to 1.1+:
# - https://github.com/elastic/elasticsearch/commit/8b0a863d427b4ebcbcfb1dcd69c996c52e7ae05e
results_size_infinity = 10000
search_courses = search.api.course_discovery_search(
search_term,
size=results_size_infinity,
)
search_courses_ids = {course['data']['id'] for course in search_courses['results']}
return LazySequence(
(
course for course in course_queryset
if six.text_type(course.id) in search_courses_ids
),
est_len=len(course_queryset)
)
def list_courses(request, username, org=None, filter_=None, search_term=None):
"""
Yield all available courses.
The courses returned are all be visible to the user identified by
`username` and the logged in user should have permission to view courses
available to that user.
Arguments:
request (HTTPRequest):
Used to identify the logged-in user and to instantiate the course
module to retrieve the course about description
username (string):
The name of the user the logged-in user would like to be
identified as
Keyword Arguments:
org (string):
If specified, visible `CourseOverview` objects are filtered
such that only those belonging to the organization with the provided
org code (e.g., "HarvardX") are returned. Case-insensitive.
filter_ (dict):
If specified, visible `CourseOverview` objects are filtered
by the given key-value pairs.
search_term (string):
Search term to filter courses (used by ElasticSearch).
Return value:
Yield `CourseOverview` objects representing the collection of courses.
"""
user = get_effective_user(request.user, username)
course_qs = get_courses(user, org=org, filter_=filter_)
course_qs = _filter_by_search(course_qs, search_term)
return course_qs
def list_course_keys(request, username, role):
"""
Yield all available CourseKeys for the user having the given role.
The courses returned include those for which the user identified by
`username` has the given role. Additionally, the logged in user
should have permission to view courses available to that user.
Note: This function does not use branding to determine courses.
Arguments:
request (HTTPRequest):
Used to identify the logged-in user and to instantiate the course
module to retrieve the course about description
username (string):
The name of the user the logged-in user would like to be
identified as
Keyword Arguments:
role (string):
Course keys are filtered such that only those for which the
user has the specified role are returned.
Return value:
Yield `CourseKey` objects representing the collection of courses.
"""
user = get_effective_user(request.user, username)
course_keys = CourseOverview.get_all_course_keys()
# Global staff have access to all courses. Filter courses for non-global staff.
if GlobalStaff().has_user(user):
return course_keys
return LazySequence(
(
course_key for course_key in course_keys
if has_access(user, role, course_key)
),
est_len=len(course_keys)
)
def get_due_dates(request, course_key, user):
"""
Get due date information for a user for blocks in a course.
Arguments:
request: the request object
course_key (CourseKey): the CourseKey for the course
user: the user object for which we want due date information
Returns:
due_dates (list): a list of dictionaries containing due date information
keys:
name: the display name of the block
url: the deep link to the block
date: the due date for the block
"""
dates = get_dates_for_course(
course_key,
user,
)
store = modulestore()
due_dates = []
for (block_key, date_type), date in six.iteritems(dates):
if date_type == 'due':
try:
block_display_name = store.get_item(block_key).display_name
except ItemNotFoundError:
logger.exception('Failed to get block for due date item with key: {}'.format(block_key))
block_display_name = UNKNOWN_BLOCK_DISPLAY_NAME
# get url to the block in the course
block_url = reverse('jump_to', args=[course_key, block_key])
block_url = request.build_absolute_uri(block_url)
due_dates.append({
'name': block_display_name,
'url': block_url,
'date': date,
})
return due_dates
def get_course_run_url(request, course_id):
"""
Get the URL to a course run.
Arguments:
request: the request object
course_id (string): the course id of the course
Returns:
(string): the URL to the course run associated with course_id
"""
course_run_url = reverse('openedx.course_experience.course_home', args=[course_id])
return request.build_absolute_uri(course_run_url)