Merge pull request #23728 from edx/dcs/dashboard
Refactor instructor dashboard access
This commit is contained in:
@@ -343,6 +343,14 @@ class OrgLibraryUserRole(OrgRole):
|
||||
super(OrgLibraryUserRole, self).__init__(self.ROLE, *args, **kwargs)
|
||||
|
||||
|
||||
class OrgDataResearcherRole(OrgRole):
|
||||
"""A Data Researcher"""
|
||||
ROLE = 'data_researcher'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(OrgDataResearcherRole, self).__init__(self.ROLE, *args, **kwargs)
|
||||
|
||||
|
||||
@register_access_role
|
||||
class CourseCreatorRole(RoleBase):
|
||||
"""
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
"""
|
||||
Django rules for student roles
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import rules
|
||||
|
||||
from lms.djangoapps.courseware.access import has_access
|
||||
from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag, WaffleFlag, WaffleFlagNamespace
|
||||
|
||||
from .roles import CourseDataResearcherRole
|
||||
|
||||
# Waffle flag to enable the separate course outline page and full width content.
|
||||
RESEARCHER_ROLE = CourseWaffleFlag(WaffleFlagNamespace(name='instructor'), 'researcher')
|
||||
|
||||
|
||||
@rules.predicate
|
||||
def can_access_reports(user, course_id):
|
||||
"""
|
||||
Returns whether the user can access the course data downloads.
|
||||
"""
|
||||
is_staff = user.is_staff
|
||||
if RESEARCHER_ROLE.is_enabled(course_id):
|
||||
return is_staff or CourseDataResearcherRole(course_id).has_user(user)
|
||||
else:
|
||||
return is_staff or has_access(user, 'staff', course_id)
|
||||
|
||||
rules.add_perm('student.can_research', can_access_reports)
|
||||
@@ -19,10 +19,12 @@ from xblock.core import XBlock
|
||||
from course_modes.models import CourseMode
|
||||
from openedx.core.djangoapps.content.course_overviews.models import CourseOverview
|
||||
from student.models import CourseAccessRole, CourseEnrollment
|
||||
from student.roles import CourseRole, OrgRole
|
||||
from xmodule.course_module import CourseDescriptor
|
||||
from xmodule.error_module import ErrorDescriptor
|
||||
from xmodule.x_module import XModule
|
||||
|
||||
|
||||
from .access import has_access
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@@ -151,3 +153,27 @@ class HasStaffAccessToContent(Rule):
|
||||
if not is_global_staff:
|
||||
query &= Q(id__in=course_staff_or_instructor_courses) | Q(org__in=org_staff_or_instructor_courses)
|
||||
return query
|
||||
|
||||
|
||||
class HasRolesRule(Rule):
|
||||
def __init__(self, *roles):
|
||||
self.roles = roles
|
||||
|
||||
def check(self, user, instance=None):
|
||||
if not user.is_authenticated:
|
||||
return False
|
||||
if isinstance(instance, CourseKey):
|
||||
course_key = instance
|
||||
elif isinstance(instance, (CourseDescriptor, CourseOverview)):
|
||||
course_key = instance.id
|
||||
elif isinstance(instance, (ErrorDescriptor, XModule, XBlock)):
|
||||
course_key = instance.scope_ids.usage_id.course_key
|
||||
else:
|
||||
course_key = CourseKey.from_string(str(instance))
|
||||
|
||||
for role in self.roles:
|
||||
if CourseRole(role, course_key).has_user(user):
|
||||
return True
|
||||
if OrgRole(role, course_key.org).has_user(user):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -4,7 +4,8 @@ Permissions for the instructor dashboard and associated actions
|
||||
|
||||
from bridgekeeper import perms
|
||||
from bridgekeeper.rules import is_staff
|
||||
from lms.djangoapps.courseware.rules import HasAccessRule
|
||||
from lms.djangoapps.courseware.rules import HasAccessRule, HasRolesRule
|
||||
|
||||
|
||||
ALLOW_STUDENT_TO_BYPASS_ENTRANCE_EXAM = 'instructor.allow_student_to_bypass_entrance_exam'
|
||||
ASSIGN_TO_COHORTS = 'instructor.assign_to_cohorts'
|
||||
@@ -16,6 +17,18 @@ GENERATE_CERTIFICATE_EXCEPTIONS = 'instructor.generate_certificate_exceptions'
|
||||
GENERATE_BULK_CERTIFICATE_EXCEPTIONS = 'instructor.generate_bulk_certificate_exceptions'
|
||||
GIVE_STUDENT_EXTENSION = 'instructor.give_student_extension'
|
||||
VIEW_ISSUED_CERTIFICATES = 'instructor.view_issued_certificates'
|
||||
CAN_RESEARCH = 'instructor.research'
|
||||
CAN_ENROLL = 'instructor.enroll'
|
||||
CAN_BETATEST = 'instructor.enroll_beta'
|
||||
ENROLLMENT_REPORT = 'instructor.enrollment_report'
|
||||
EXAM_RESULTS = 'instructor.view_exam_results'
|
||||
OVERRIDE_GRADES = 'instructor.override_grades'
|
||||
SHOW_TASKS = 'instructor.show_tasks'
|
||||
VIEW_COUPONS = 'instructor.view_coupons'
|
||||
EMAIL = 'instructor.email'
|
||||
RESCORE_EXAMS = 'instructor.rescore_exams'
|
||||
VIEW_REGISTRATION = 'instructor.view_registration'
|
||||
VIEW_DASHBOARD = 'instructor.dashboard'
|
||||
|
||||
|
||||
perms[ALLOW_STUDENT_TO_BYPASS_ENTRANCE_EXAM] = HasAccessRule('staff')
|
||||
@@ -27,4 +40,21 @@ perms[ENABLE_CERTIFICATE_GENERATION] = is_staff
|
||||
perms[GENERATE_CERTIFICATE_EXCEPTIONS] = is_staff
|
||||
perms[GENERATE_BULK_CERTIFICATE_EXCEPTIONS] = is_staff
|
||||
perms[GIVE_STUDENT_EXTENSION] = HasAccessRule('staff')
|
||||
perms[VIEW_ISSUED_CERTIFICATES] = HasAccessRule('staff')
|
||||
perms[VIEW_ISSUED_CERTIFICATES] = HasAccessRule('staff') | HasRolesRule('data_researcher')
|
||||
perms[CAN_RESEARCH] = HasRolesRule('data_researcher')
|
||||
perms[CAN_ENROLL] = HasAccessRule('staff')
|
||||
perms[CAN_BETATEST] = HasAccessRule('instructor')
|
||||
perms[ENROLLMENT_REPORT] = HasAccessRule('staff') | HasRolesRule('data_researcher')
|
||||
perms[VIEW_COUPONS] = HasAccessRule('staff') | HasRolesRule('data_researcher')
|
||||
perms[EXAM_RESULTS] = HasAccessRule('staff')
|
||||
perms[OVERRIDE_GRADES] = HasAccessRule('staff')
|
||||
perms[SHOW_TASKS] = HasAccessRule('staff') | HasRolesRule('data_researcher')
|
||||
perms[EMAIL] = HasAccessRule('staff')
|
||||
perms[RESCORE_EXAMS] = HasAccessRule('instructor')
|
||||
perms[VIEW_REGISTRATION] = HasAccessRule('staff')
|
||||
perms[VIEW_DASHBOARD] = \
|
||||
HasRolesRule(
|
||||
'staff',
|
||||
'instructor',
|
||||
'data_researcher'
|
||||
) | HasAccessRule('staff') | HasAccessRule('instructor')
|
||||
|
||||
@@ -31,7 +31,7 @@ from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
|
||||
from shoppingcart.models import CourseRegCodeItem, Order, PaidCourseRegistration
|
||||
from student.models import CourseEnrollment
|
||||
from student.roles import CourseFinanceAdminRole
|
||||
from student.tests.factories import AdminFactory, CourseEnrollmentFactory
|
||||
from student.tests.factories import AdminFactory, CourseAccessRoleFactory, CourseEnrollmentFactory
|
||||
from xmodule.modulestore import ModuleStoreEnum
|
||||
from xmodule.modulestore.tests.django_utils import TEST_DATA_SPLIT_MODULESTORE, ModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, check_mongo_calls
|
||||
@@ -118,6 +118,24 @@ class TestInstructorDashboard(ModuleStoreTestCase, LoginEnrollmentTestCase, XssT
|
||||
student = UserFactory.create()
|
||||
self.assertFalse(has_instructor_tab(student, self.course))
|
||||
|
||||
researcher = UserFactory.create()
|
||||
CourseAccessRoleFactory(
|
||||
course_id=self.course.id,
|
||||
user=researcher,
|
||||
role='data_researcher',
|
||||
org=self.course.id.org
|
||||
)
|
||||
self.assertTrue(has_instructor_tab(researcher, self.course))
|
||||
|
||||
org_researcher = UserFactory.create()
|
||||
CourseAccessRoleFactory(
|
||||
course_id=None,
|
||||
user=org_researcher,
|
||||
role='data_researcher',
|
||||
org=self.course.id.org
|
||||
)
|
||||
self.assertTrue(has_instructor_tab(org_researcher, self.course))
|
||||
|
||||
@ddt.data(
|
||||
("How to defeat the Road Runner", "2017", "001", "ACME"),
|
||||
)
|
||||
|
||||
@@ -37,7 +37,8 @@ from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthenticat
|
||||
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser
|
||||
from opaque_keys import InvalidKeyError
|
||||
from opaque_keys.edx.keys import CourseKey, UsageKey
|
||||
from rest_framework import permissions, status
|
||||
from rest_framework import status
|
||||
from rest_framework.permissions import IsAuthenticated, IsAdminUser
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
from six import StringIO, text_type
|
||||
@@ -135,18 +136,8 @@ from util.json_request import JsonResponse, JsonResponseBadRequest
|
||||
from util.views import require_global_staff
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
from ..permissions import (
|
||||
ALLOW_STUDENT_TO_BYPASS_ENTRANCE_EXAM,
|
||||
ASSIGN_TO_COHORTS,
|
||||
EDIT_COURSE_ACCESS,
|
||||
EDIT_FORUM_ROLES,
|
||||
EDIT_INVOICE_VALIDATION,
|
||||
ENABLE_CERTIFICATE_GENERATION,
|
||||
GENERATE_BULK_CERTIFICATE_EXCEPTIONS,
|
||||
GENERATE_CERTIFICATE_EXCEPTIONS,
|
||||
GIVE_STUDENT_EXTENSION,
|
||||
VIEW_ISSUED_CERTIFICATES
|
||||
)
|
||||
from .. import permissions
|
||||
|
||||
from .tools import (
|
||||
dump_module_extensions,
|
||||
dump_student_extensions,
|
||||
@@ -231,36 +222,6 @@ def require_post_params(*args, **kwargs):
|
||||
return decorator
|
||||
|
||||
|
||||
def require_level(level, perm=None):
|
||||
"""
|
||||
Decorator with argument that requires an access level of the requesting
|
||||
user. If the requirement is not satisfied, returns an
|
||||
HttpResponseForbidden (403).
|
||||
|
||||
Assumes that request is in args[0].
|
||||
Assumes that course_id is in kwargs['course_id'].
|
||||
|
||||
`level` is in ['instructor', 'staff']
|
||||
if `level` is 'staff', instructors will also be allowed, even
|
||||
if they are not in the staff group.
|
||||
"""
|
||||
if level not in ['instructor', 'staff']:
|
||||
raise ValueError(u"unrecognized level '{}'".format(level))
|
||||
|
||||
def decorator(func): # pylint: disable=missing-docstring
|
||||
def wrapped(*args, **kwargs):
|
||||
request = args[0]
|
||||
course = get_course_by_id(CourseKey.from_string(kwargs['course_id']))
|
||||
|
||||
if has_access(request.user, level, course) and \
|
||||
((perm and request.user.has_perm(perm, course.id)) or not perm):
|
||||
return func(*args, **kwargs)
|
||||
else:
|
||||
return HttpResponseForbidden()
|
||||
return wrapped
|
||||
return decorator
|
||||
|
||||
|
||||
def require_course_permission(permission):
|
||||
"""
|
||||
Decorator with argument that requires a specific permission of the requesting
|
||||
@@ -342,7 +303,7 @@ COUNTRY_INDEX = 3
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.CAN_ENROLL)
|
||||
def register_and_enroll_students(request, course_id): # pylint: disable=too-many-statements
|
||||
"""
|
||||
Create new account and Enroll students in this course.
|
||||
@@ -658,7 +619,7 @@ def create_and_enroll_user(email, username, name, country, password, course_id,
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.CAN_ENROLL)
|
||||
@require_post_params(action="enroll or unenroll", identifiers="stringified list of emails and/or usernames")
|
||||
def students_update_enrollment(request, course_id):
|
||||
"""
|
||||
@@ -826,7 +787,7 @@ def students_update_enrollment(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('instructor')
|
||||
@require_course_permission(permissions.CAN_BETATEST)
|
||||
@common_exceptions_400
|
||||
@require_post_params(
|
||||
identifiers="stringified list of emails and/or usernames",
|
||||
@@ -910,7 +871,7 @@ def bulk_beta_modify_access(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_course_permission(EDIT_COURSE_ACCESS)
|
||||
@require_course_permission(permissions.EDIT_COURSE_ACCESS)
|
||||
@require_post_params(
|
||||
unique_student_identifier="email or username of user to change access",
|
||||
rolename="'instructor', 'staff', 'beta', or 'ccx_coach'",
|
||||
@@ -991,7 +952,7 @@ def modify_access(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('instructor')
|
||||
@require_course_permission(permissions.EDIT_COURSE_ACCESS)
|
||||
@require_post_params(rolename="'instructor', 'staff', or 'beta'")
|
||||
def list_course_role_members(request, course_id):
|
||||
"""
|
||||
@@ -1045,7 +1006,7 @@ def list_course_role_members(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff', perm='student.can_research')
|
||||
@require_course_permission(permissions.CAN_RESEARCH)
|
||||
@common_exceptions_400
|
||||
def get_problem_responses(request, course_id):
|
||||
"""
|
||||
@@ -1085,15 +1046,17 @@ def get_problem_responses(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.CAN_RESEARCH)
|
||||
def get_grading_config(request, course_id):
|
||||
"""
|
||||
Respond with json which contains a html formatted grade summary.
|
||||
"""
|
||||
course_id = CourseKey.from_string(course_id)
|
||||
course = get_course_with_access(
|
||||
request.user, 'staff', course_id, depth=None
|
||||
)
|
||||
# course = get_course_with_access(
|
||||
# request.user, 'staff', course_id, depth=None
|
||||
# )
|
||||
course = get_course_by_id(course_id)
|
||||
|
||||
grading_config_summary = instructor_analytics.basic.dump_grading_context(course)
|
||||
|
||||
response_payload = {
|
||||
@@ -1105,7 +1068,7 @@ def get_grading_config(request, course_id):
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff', perm='student.can_research')
|
||||
@require_course_permission(permissions.CAN_RESEARCH)
|
||||
def get_sale_records(request, course_id, csv=False): # pylint: disable=unused-argument, redefined-outer-name
|
||||
"""
|
||||
return the summary of all sales records for a particular course
|
||||
@@ -1136,7 +1099,7 @@ def get_sale_records(request, course_id, csv=False): # pylint: disable=unused-a
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff', perm='student.can_research')
|
||||
@require_course_permission(permissions.CAN_RESEARCH)
|
||||
def get_sale_order_records(request, course_id): # pylint: disable=unused-argument
|
||||
"""
|
||||
return the summary of all sales records for a particular course
|
||||
@@ -1176,7 +1139,7 @@ def get_sale_order_records(request, course_id): # pylint: disable=unused-argume
|
||||
return instructor_analytics.csvs.create_csv_response("e-commerce_sale_order_records.csv", csv_columns, datarows)
|
||||
|
||||
|
||||
@require_course_permission(EDIT_INVOICE_VALIDATION)
|
||||
@require_course_permission(permissions.EDIT_INVOICE_VALIDATION)
|
||||
@require_POST
|
||||
def sale_validation(request, course_id):
|
||||
"""
|
||||
@@ -1243,7 +1206,7 @@ def re_validate_invoice(obj_invoice):
|
||||
@transaction.non_atomic_requests
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_course_permission(VIEW_ISSUED_CERTIFICATES)
|
||||
@require_course_permission(permissions.VIEW_ISSUED_CERTIFICATES)
|
||||
def get_issued_certificates(request, course_id):
|
||||
"""
|
||||
Responds with JSON if CSV is not required. contains a list of issued certificates.
|
||||
@@ -1284,7 +1247,7 @@ def get_issued_certificates(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff', perm='student.can_research')
|
||||
@require_course_permission(permissions.CAN_RESEARCH)
|
||||
@common_exceptions_400
|
||||
def get_students_features(request, course_id, csv=False): # pylint: disable=redefined-outer-name
|
||||
"""
|
||||
@@ -1381,7 +1344,7 @@ def get_students_features(request, course_id, csv=False): # pylint: disable=red
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.CAN_RESEARCH)
|
||||
@common_exceptions_400
|
||||
def get_students_who_may_enroll(request, course_id):
|
||||
"""
|
||||
@@ -1428,7 +1391,7 @@ def _cohorts_csv_validator(file_storage, file_to_validate):
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_POST
|
||||
@require_course_permission(ASSIGN_TO_COHORTS)
|
||||
@require_course_permission(permissions.ASSIGN_TO_COHORTS)
|
||||
@common_exceptions_400
|
||||
def add_users_to_cohorts(request, course_id):
|
||||
"""
|
||||
@@ -1474,7 +1437,7 @@ class CohortCSV(DeveloperErrorViewMixin, APIView):
|
||||
BearerAuthenticationAllowInactiveUser,
|
||||
SessionAuthenticationAllowInactiveUser,
|
||||
)
|
||||
permission_classes = (permissions.IsAuthenticated, permissions.IsAdminUser)
|
||||
permission_classes = (IsAuthenticated, IsAdminUser)
|
||||
|
||||
def post(self, request, course_key_string):
|
||||
"""
|
||||
@@ -1498,7 +1461,7 @@ class CohortCSV(DeveloperErrorViewMixin, APIView):
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.VIEW_COUPONS)
|
||||
def get_coupon_codes(request, course_id): # pylint: disable=unused-argument
|
||||
"""
|
||||
Respond with csv which contains a summary of all Active Coupons.
|
||||
@@ -1529,7 +1492,7 @@ def get_coupon_codes(request, course_id): # pylint: disable=unused-argument
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.ENROLLMENT_REPORT)
|
||||
@require_finance_admin
|
||||
@common_exceptions_400
|
||||
def get_enrollment_report(request, course_id):
|
||||
@@ -1548,7 +1511,7 @@ def get_enrollment_report(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.ENROLLMENT_REPORT)
|
||||
@require_finance_admin
|
||||
@common_exceptions_400
|
||||
def get_exec_summary_report(request, course_id):
|
||||
@@ -1567,7 +1530,7 @@ def get_exec_summary_report(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.ENROLLMENT_REPORT)
|
||||
@common_exceptions_400
|
||||
def get_course_survey_results(request, course_id):
|
||||
"""
|
||||
@@ -1585,7 +1548,7 @@ def get_course_survey_results(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.EXAM_RESULTS)
|
||||
@common_exceptions_400
|
||||
def get_proctored_exam_results(request, course_id):
|
||||
"""
|
||||
@@ -1674,7 +1637,7 @@ def random_code_generator():
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.VIEW_COUPONS)
|
||||
@require_POST
|
||||
def get_registration_codes(request, course_id):
|
||||
"""
|
||||
@@ -1869,7 +1832,7 @@ def generate_registration_codes(request, course_id):
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.VIEW_COUPONS)
|
||||
@require_POST
|
||||
def active_registration_codes(request, course_id):
|
||||
"""
|
||||
@@ -1900,7 +1863,7 @@ def active_registration_codes(request, course_id):
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.VIEW_COUPONS)
|
||||
@require_POST
|
||||
def spent_registration_codes(request, course_id):
|
||||
"""
|
||||
@@ -1931,7 +1894,7 @@ def spent_registration_codes(request, course_id):
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff', perm='student.can_research')
|
||||
@require_course_permission(permissions.CAN_RESEARCH)
|
||||
def get_anon_ids(request, course_id): # pylint: disable=unused-argument
|
||||
"""
|
||||
Respond with 2-column CSV output of user-id, anonymized-user-id
|
||||
@@ -1969,7 +1932,7 @@ def get_anon_ids(request, course_id): # pylint: disable=unused-argument
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.CAN_ENROLL)
|
||||
@require_post_params(
|
||||
unique_student_identifier="email or username of student for whom to get enrollment status"
|
||||
)
|
||||
@@ -2026,7 +1989,7 @@ def get_student_enrollment_status(request, course_id):
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@common_exceptions_400
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.ENROLLMENT_REPORT)
|
||||
@require_post_params(
|
||||
unique_student_identifier="email or username of student for whom to get progress url"
|
||||
)
|
||||
@@ -2057,7 +2020,7 @@ def get_student_progress_url(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.GIVE_STUDENT_EXTENSION)
|
||||
@require_post_params(
|
||||
problem_to_reset="problem urlname to reset"
|
||||
)
|
||||
@@ -2144,7 +2107,7 @@ def reset_student_attempts(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.GIVE_STUDENT_EXTENSION)
|
||||
@common_exceptions_400
|
||||
def reset_student_attempts_for_entrance_exam(request, course_id):
|
||||
"""
|
||||
@@ -2219,7 +2182,7 @@ def reset_student_attempts_for_entrance_exam(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.OVERRIDE_GRADES)
|
||||
@require_post_params(problem_to_reset="problem urlname to reset")
|
||||
@common_exceptions_400
|
||||
def rescore_problem(request, course_id):
|
||||
@@ -2295,7 +2258,7 @@ def rescore_problem(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.OVERRIDE_GRADES)
|
||||
@require_post_params(problem_to_reset="problem urlname to reset", score='overriding score')
|
||||
@common_exceptions_400
|
||||
def override_problem_score(request, course_id):
|
||||
@@ -2352,7 +2315,7 @@ def override_problem_score(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('instructor')
|
||||
@require_course_permission(permissions.RESCORE_EXAMS)
|
||||
@common_exceptions_400
|
||||
def rescore_entrance_exam(request, course_id):
|
||||
"""
|
||||
@@ -2409,7 +2372,7 @@ def rescore_entrance_exam(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.EMAIL)
|
||||
def list_background_email_tasks(request, course_id): # pylint: disable=unused-argument
|
||||
"""
|
||||
List background email tasks.
|
||||
@@ -2431,7 +2394,7 @@ def list_background_email_tasks(request, course_id): # pylint: disable=unused-a
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.EMAIL)
|
||||
def list_email_content(request, course_id): # pylint: disable=unused-argument
|
||||
"""
|
||||
List the content of bulk emails sent
|
||||
@@ -2450,7 +2413,7 @@ def list_email_content(request, course_id): # pylint: disable=unused-argument
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.SHOW_TASKS)
|
||||
def list_instructor_tasks(request, course_id):
|
||||
"""
|
||||
List instructor tasks.
|
||||
@@ -2496,7 +2459,7 @@ def list_instructor_tasks(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.SHOW_TASKS)
|
||||
def list_entrance_exam_instructor_tasks(request, course_id):
|
||||
"""
|
||||
List entrance exam related instructor tasks.
|
||||
@@ -2538,7 +2501,7 @@ def list_entrance_exam_instructor_tasks(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.CAN_RESEARCH)
|
||||
def list_report_downloads(request, course_id):
|
||||
"""
|
||||
List grade CSV files that are available for download for this course.
|
||||
@@ -2562,7 +2525,7 @@ def list_report_downloads(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff', perm='student.can_research')
|
||||
@require_course_permission(permissions.CAN_RESEARCH)
|
||||
@require_finance_admin
|
||||
def list_financial_report_downloads(_request, course_id):
|
||||
"""
|
||||
@@ -2584,7 +2547,7 @@ def list_financial_report_downloads(_request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff', perm='student.can_research')
|
||||
@require_course_permission(permissions.CAN_RESEARCH)
|
||||
@common_exceptions_400
|
||||
def export_ora2_data(request, course_id):
|
||||
"""
|
||||
@@ -2602,7 +2565,7 @@ def export_ora2_data(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff', perm='student.can_research')
|
||||
@require_course_permission(permissions.CAN_RESEARCH)
|
||||
@common_exceptions_400
|
||||
def calculate_grades_csv(request, course_id):
|
||||
"""
|
||||
@@ -2620,7 +2583,7 @@ def calculate_grades_csv(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff', perm='student.can_research')
|
||||
@require_course_permission(permissions.CAN_RESEARCH)
|
||||
@common_exceptions_400
|
||||
def problem_grade_report(request, course_id):
|
||||
"""
|
||||
@@ -2641,7 +2604,7 @@ def problem_grade_report(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.CAN_ENROLL)
|
||||
@require_post_params('rolename')
|
||||
def list_forum_members(request, course_id):
|
||||
"""
|
||||
@@ -2713,7 +2676,7 @@ def list_forum_members(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.EMAIL)
|
||||
@require_post_params(send_to="sending to whom", subject="subject line", message="message text")
|
||||
@common_exceptions_400
|
||||
def send_email(request, course_id):
|
||||
@@ -2789,7 +2752,7 @@ def send_email(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_course_permission(EDIT_FORUM_ROLES)
|
||||
@require_course_permission(permissions.EDIT_FORUM_ROLES)
|
||||
@require_post_params(
|
||||
unique_student_identifier="email or username of user to change access",
|
||||
rolename="the forum role",
|
||||
@@ -2882,7 +2845,7 @@ def _display_unit(unit):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_course_permission(GIVE_STUDENT_EXTENSION)
|
||||
@require_course_permission(permissions.GIVE_STUDENT_EXTENSION)
|
||||
@require_post_params('student', 'url', 'due_datetime')
|
||||
def change_due_date(request, course_id):
|
||||
"""
|
||||
@@ -2906,7 +2869,7 @@ def change_due_date(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_course_permission(GIVE_STUDENT_EXTENSION)
|
||||
@require_course_permission(permissions.GIVE_STUDENT_EXTENSION)
|
||||
@require_post_params('student', 'url')
|
||||
def reset_due_date(request, course_id):
|
||||
"""
|
||||
@@ -2935,7 +2898,7 @@ def reset_due_date(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.GIVE_STUDENT_EXTENSION)
|
||||
@require_post_params('url')
|
||||
def show_unit_extensions(request, course_id):
|
||||
"""
|
||||
@@ -2950,7 +2913,7 @@ def show_unit_extensions(request, course_id):
|
||||
@require_POST
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.GIVE_STUDENT_EXTENSION)
|
||||
@require_post_params('student')
|
||||
def show_student_extensions(request, course_id):
|
||||
"""
|
||||
@@ -3017,7 +2980,7 @@ def generate_example_certificates(request, course_id=None): # pylint: disable=u
|
||||
return redirect(_instructor_dash_url(course_key, section='certificates'))
|
||||
|
||||
|
||||
@require_course_permission(ENABLE_CERTIFICATE_GENERATION)
|
||||
@require_course_permission(permissions.ENABLE_CERTIFICATE_GENERATION)
|
||||
@require_POST
|
||||
def enable_certificate_generation(request, course_id=None):
|
||||
"""Enable/disable self-generated certificates for a course.
|
||||
@@ -3037,7 +3000,7 @@ def enable_certificate_generation(request, course_id=None):
|
||||
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_course_permission(ALLOW_STUDENT_TO_BYPASS_ENTRANCE_EXAM)
|
||||
@require_course_permission(permissions.ALLOW_STUDENT_TO_BYPASS_ENTRANCE_EXAM)
|
||||
@require_POST
|
||||
def mark_student_can_skip_entrance_exam(request, course_id):
|
||||
"""
|
||||
@@ -3301,7 +3264,7 @@ def get_student(username_or_email, course_key):
|
||||
@transaction.non_atomic_requests
|
||||
@ensure_csrf_cookie
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_course_permission(GENERATE_CERTIFICATE_EXCEPTIONS)
|
||||
@require_course_permission(permissions.GENERATE_CERTIFICATE_EXCEPTIONS)
|
||||
@require_POST
|
||||
@common_exceptions_400
|
||||
def generate_certificate_exceptions(request, course_id, generate_for=None):
|
||||
@@ -3343,7 +3306,7 @@ def generate_certificate_exceptions(request, course_id, generate_for=None):
|
||||
|
||||
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_course_permission(GENERATE_BULK_CERTIFICATE_EXCEPTIONS)
|
||||
@require_course_permission(permissions.GENERATE_BULK_CERTIFICATE_EXCEPTIONS)
|
||||
@require_POST
|
||||
def generate_bulk_certificate_exceptions(request, course_id):
|
||||
"""
|
||||
|
||||
@@ -6,7 +6,6 @@ which is currently use by ccx and instructor apps.
|
||||
|
||||
import math
|
||||
|
||||
import six
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import transaction
|
||||
from django.urls import reverse
|
||||
@@ -16,9 +15,11 @@ from opaque_keys.edx.keys import CourseKey
|
||||
from lms.djangoapps.courseware.courses import get_course_with_access
|
||||
from edxmako.shortcuts import render_to_response
|
||||
from lms.djangoapps.grades.api import CourseGradeFactory
|
||||
from lms.djangoapps.instructor.views.api import require_level
|
||||
from lms.djangoapps.instructor.views.api import require_course_permission
|
||||
from xmodule.modulestore.django import modulestore
|
||||
|
||||
from .. import permissions
|
||||
|
||||
# Grade book: max students per page
|
||||
MAX_STUDENTS_PER_PAGE_GRADE_BOOK = 20
|
||||
|
||||
@@ -101,7 +102,7 @@ def get_grade_book_page(request, course, course_key):
|
||||
|
||||
@transaction.non_atomic_requests
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.OVERRIDE_GRADES)
|
||||
def spoc_gradebook(request, course_id):
|
||||
"""
|
||||
Show the gradebook for this course:
|
||||
@@ -114,7 +115,7 @@ def spoc_gradebook(request, course_id):
|
||||
|
||||
return render_to_response('courseware/gradebook.html', {
|
||||
'page': page,
|
||||
'page_url': reverse('spoc_gradebook', kwargs={'course_id': six.text_type(course_key)}),
|
||||
'page_url': reverse('spoc_gradebook', kwargs={'course_id': str(course_key)}),
|
||||
'students': student_info,
|
||||
'course': course,
|
||||
'course_id': course_key,
|
||||
|
||||
@@ -65,6 +65,7 @@ from xmodule.modulestore.django import modulestore
|
||||
from xmodule.tabs import CourseTab
|
||||
|
||||
from .tools import get_units_with_due_date, title_or_url
|
||||
from .. import permissions
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -84,7 +85,7 @@ class InstructorDashboardTab(CourseTab):
|
||||
"""
|
||||
Returns true if the specified user has staff access.
|
||||
"""
|
||||
return bool(user and has_access(user, 'staff', course, course.id))
|
||||
return bool(user and user.is_authenticated and user.has_perm(permissions.VIEW_DASHBOARD, course.id))
|
||||
|
||||
|
||||
def show_analytics_dashboard_message(course_key):
|
||||
@@ -120,24 +121,27 @@ def instructor_dashboard_2(request, course_id):
|
||||
'sales_admin': CourseSalesAdminRole(course_key).has_user(request.user),
|
||||
'staff': bool(has_access(request.user, 'staff', course)),
|
||||
'forum_admin': has_forum_access(request.user, course_key, FORUM_ROLE_ADMINISTRATOR),
|
||||
'data_researcher': request.user.has_perm('student.can_research', course_key),
|
||||
'data_researcher': request.user.has_perm(permissions.CAN_RESEARCH, course_key),
|
||||
}
|
||||
|
||||
if not access['staff']:
|
||||
if not request.user.has_perm(permissions.VIEW_DASHBOARD, course_key):
|
||||
raise Http404()
|
||||
|
||||
is_white_label = CourseMode.is_white_label(course_key)
|
||||
|
||||
reports_enabled = configuration_helpers.get_value('SHOW_ECOMMERCE_REPORTS', False)
|
||||
|
||||
sections = [
|
||||
_section_course_info(course, access),
|
||||
_section_membership(course, access),
|
||||
_section_cohort_management(course, access),
|
||||
_section_discussions_management(course, access),
|
||||
_section_student_admin(course, access),
|
||||
_section_data_download(course, access),
|
||||
]
|
||||
sections = []
|
||||
if access['staff']:
|
||||
sections.extend([
|
||||
_section_course_info(course, access),
|
||||
_section_membership(course, access),
|
||||
_section_cohort_management(course, access),
|
||||
_section_discussions_management(course, access),
|
||||
_section_student_admin(course, access),
|
||||
])
|
||||
if access['data_researcher']:
|
||||
sections.append(_section_data_download(course, access))
|
||||
|
||||
analytics_dashboard_message = None
|
||||
if show_analytics_dashboard_message(course_key):
|
||||
@@ -207,7 +211,7 @@ def instructor_dashboard_2(request, course_id):
|
||||
openassessment_blocks = [
|
||||
block for block in openassessment_blocks if block.parent is not None
|
||||
]
|
||||
if len(openassessment_blocks) > 0:
|
||||
if len(openassessment_blocks) > 0 and access['staff']:
|
||||
sections.append(_section_open_response_assessment(request, course, openassessment_blocks, access))
|
||||
|
||||
disable_buttons = not _is_small_course(course_key)
|
||||
|
||||
@@ -5,7 +5,6 @@ E-commerce Tab Instructor Dashboard Query Registration Code Status.
|
||||
|
||||
import logging
|
||||
|
||||
import six
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.decorators.cache import cache_control
|
||||
@@ -14,16 +13,18 @@ from opaque_keys.edx.locator import CourseKey
|
||||
|
||||
from lms.djangoapps.courseware.courses import get_course_by_id
|
||||
from lms.djangoapps.instructor.enrollment import get_email_params, send_mail_to_student
|
||||
from lms.djangoapps.instructor.views.api import require_level
|
||||
from lms.djangoapps.instructor.views.api import require_course_permission
|
||||
from shoppingcart.models import CourseRegistrationCode, RegistrationCodeRedemption
|
||||
from student.models import CourseEnrollment
|
||||
from util.json_request import JsonResponse
|
||||
|
||||
from .. import permissions
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.VIEW_REGISTRATION)
|
||||
@require_GET
|
||||
def look_up_registration_code(request, course_id):
|
||||
"""
|
||||
@@ -47,7 +48,7 @@ def look_up_registration_code(request, course_id):
|
||||
|
||||
reg_code_already_redeemed = RegistrationCodeRedemption.is_registration_code_redeemed(code)
|
||||
|
||||
registration_code_detail_url = reverse('registration_code_details', kwargs={'course_id': six.text_type(course_id)})
|
||||
registration_code_detail_url = reverse('registration_code_details', kwargs={'course_id': str(course_id)})
|
||||
|
||||
return JsonResponse({
|
||||
'is_registration_code_exists': True,
|
||||
@@ -58,7 +59,7 @@ def look_up_registration_code(request, course_id):
|
||||
|
||||
|
||||
@cache_control(no_cache=True, no_store=True, must_revalidate=True)
|
||||
@require_level('staff')
|
||||
@require_course_permission(permissions.VIEW_REGISTRATION)
|
||||
@require_POST
|
||||
def registration_code_details(request, course_id):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user