Commit e1fcf4c3 by David Ormsbee

Merge pull request #1478 from MITx/bugfix/dave/too_many_sql_queries

Reduce SQL queries in courseware by model caching a User's groups
parents c581ecd8 1d4defd0
"""This file contains (or should), all access control logic for the courseware. """This file contains (or should), all access control logic for the courseware.
Ideally, it will be the only place that needs to know about any special settings Ideally, it will be the only place that needs to know about any special settings
like DISABLE_START_DATES""" like DISABLE_START_DATES"""
import logging import logging
import time import time
from datetime import datetime, timedelta from datetime import datetime, timedelta
from functools import partial
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import Group from django.contrib.auth.models import Group
...@@ -363,21 +363,15 @@ def _course_org_staff_group_name(location, course_context=None): ...@@ -363,21 +363,15 @@ def _course_org_staff_group_name(location, course_context=None):
return 'staff_%s' % course_id.split('/')[0] return 'staff_%s' % course_id.split('/')[0]
def _course_staff_group_name(location, course_context=None): def group_names_for(role, location, course_context=None):
""" """Returns the group names for a given role with this location. Plural
Get the name of the staff group for a location in the context of a course run. because it will return both the name we expect now as well as the legacy
group name we support for backwards compatibility. This should not check
location: something that can passed to Location the DB for existence of a group (like some of its callers do) because that's
course_context: A course_id that specifies the course run in which the location occurs. a DB roundtrip, and we expect this might be invoked many times as we crawl
Required if location doesn't have category 'course' an XModule tree."""
cdodge: We're changing the name convention of the group to better epxress different runs of courses by
using course_id rather than just the course number. So first check to see if the group name exists
"""
loc = Location(location) loc = Location(location)
legacy_name = 'staff_%s' % loc.course legacy_group_name = '{0}_{1}'.format(role, loc.course)
if _does_course_group_name_exist(legacy_name):
return legacy_name
if loc.category == 'course': if loc.category == 'course':
course_id = loc.course_id course_id = loc.course_id
...@@ -386,22 +380,31 @@ def _course_staff_group_name(location, course_context=None): ...@@ -386,22 +380,31 @@ def _course_staff_group_name(location, course_context=None):
raise CourseContextRequired() raise CourseContextRequired()
course_id = course_context course_id = course_context
return 'staff_%s' % course_id group_name = '{0}_{1}'.format(role, course_id)
return [group_name, legacy_group_name]
def course_beta_test_group_name(location): group_names_for_staff = partial(group_names_for, 'staff')
group_names_for_instructor = partial(group_names_for, 'instructor')
def _course_staff_group_name(location, course_context=None):
""" """
Get the name of the beta tester group for a location. Right now, that's Get the name of the staff group for a location in the context of a course run.
beta_testers_COURSE.
location: something that can passed to Location. location: something that can passed to Location
course_context: A course_id that specifies the course run in which the location occurs.
Required if location doesn't have category 'course'
cdodge: We're changing the name convention of the group to better epxress different runs of courses by
using course_id rather than just the course number. So first check to see if the group name exists
""" """
return 'beta_testers_{0}'.format(Location(location).course) loc = Location(location)
group_name, legacy_group_name = group_names_for_staff(location, course_context)
# nosetests thinks that anything with _test_ in the name is a test. if _does_course_group_name_exist(legacy_group_name):
# Correct this (https://nose.readthedocs.org/en/latest/finding_tests.html) return legacy_group_name
course_beta_test_group_name.__test__ = False
return group_name
def _course_org_instructor_group_name(location, course_context=None): def _course_org_instructor_group_name(location, course_context=None):
""" """
...@@ -437,18 +440,26 @@ def _course_instructor_group_name(location, course_context=None): ...@@ -437,18 +440,26 @@ def _course_instructor_group_name(location, course_context=None):
using course_id rather than just the course number. So first check to see if the group name exists using course_id rather than just the course number. So first check to see if the group name exists
""" """
loc = Location(location) loc = Location(location)
legacy_name = 'instructor_%s' % loc.course group_name, legacy_group_name = group_names_for_instructor(location, course_context)
if _does_course_group_name_exist(legacy_name):
return legacy_name
if loc.category == 'course': if _does_course_group_name_exist(legacy_group_name):
course_id = loc.course_id return legacy_group_name
else:
if course_context is None: return group_name
raise CourseContextRequired()
course_id = course_context def course_beta_test_group_name(location):
"""
Get the name of the beta tester group for a location. Right now, that's
beta_testers_COURSE.
location: something that can passed to Location.
"""
return 'beta_testers_{0}'.format(Location(location).course)
# nosetests thinks that anything with _test_ in the name is a test.
# Correct this (https://nose.readthedocs.org/en/latest/finding_tests.html)
course_beta_test_group_name.__test__ = False
return 'instructor_%s' % course_id
def _has_global_staff_access(user): def _has_global_staff_access(user):
...@@ -540,23 +551,22 @@ def _has_access_to_location(user, location, access_level, course_context): ...@@ -540,23 +551,22 @@ def _has_access_to_location(user, location, access_level, course_context):
user_groups = [g.name for g in user.groups.all()] user_groups = [g.name for g in user.groups.all()]
if access_level == 'staff': if access_level == 'staff':
staff_group = _course_staff_group_name(location, course_context) staff_groups = group_names_for_staff(location, course_context) + \
# org_staff_group is a group for an entire organization [_course_org_staff_group_name(location, course_context)]
org_staff_group = _course_org_staff_group_name(location, course_context) for staff_group in staff_groups:
if staff_group in user_groups or org_staff_group in user_groups: if staff_group in user_groups:
debug("Allow: user in group %s", staff_group) debug("Allow: user in group %s", staff_group)
return True return True
debug("Deny: user not in group %s", staff_group) debug("Deny: user not in groups %s", staff_groups)
if access_level == 'instructor' or access_level == 'staff': # instructors get staff privileges if access_level == 'instructor' or access_level == 'staff': # instructors get staff privileges
instructor_group = _course_instructor_group_name(location, course_context) instructor_groups = group_names_for_instructor(location, course_context) + \
instructor_staff_group = _course_org_instructor_group_name( [_course_org_instructor_group_name(location, course_context)]
location, course_context) for instructor_group in instructor_groups:
if instructor_group in user_groups or instructor_staff_group in user_groups: if instructor_group in user_groups:
debug("Allow: user in group %s", instructor_group) debug("Allow: user in group %s", instructor_group)
return True return True
debug("Deny: user not in group %s", instructor_group) debug("Deny: user not in groups %s", instructor_groups)
else: else:
log.debug("Error in access._has_access_to_location access_level=%s unknown" % access_level) log.debug("Error in access._has_access_to_location access_level=%s unknown" % access_level)
......
...@@ -86,7 +86,8 @@ def render_accordion(request, course, chapter, section): ...@@ -86,7 +86,8 @@ def render_accordion(request, course, chapter, section):
Returns the html string''' Returns the html string'''
# grab the table of contents # grab the table of contents
toc = toc_for_course(request.user, request, course, chapter, section) user = User.objects.prefetch_related("groups").get(id=request.user.id)
toc = toc_for_course(user, request, course, chapter, section)
context = dict([('toc', toc), context = dict([('toc', toc),
('course_id', course.id), ('course_id', course.id),
...@@ -250,23 +251,24 @@ def index(request, course_id, chapter=None, section=None, ...@@ -250,23 +251,24 @@ def index(request, course_id, chapter=None, section=None,
- HTTPresponse - HTTPresponse
""" """
course = get_course_with_access(request.user, course_id, 'load', depth=2) user = User.objects.prefetch_related("groups").get(id=request.user.id)
staff_access = has_access(request.user, course, 'staff') course = get_course_with_access(user, course_id, 'load', depth=2)
registered = registered_for_course(course, request.user) staff_access = has_access(user, course, 'staff')
registered = registered_for_course(course, user)
if not registered: if not registered:
# TODO (vshnayder): do course instructors need to be registered to see course? # TODO (vshnayder): do course instructors need to be registered to see course?
log.debug('User %s tried to view course %s but is not enrolled' % (request.user, course.location.url())) log.debug('User %s tried to view course %s but is not enrolled' % (user, course.location.url()))
return redirect(reverse('about_course', args=[course.id])) return redirect(reverse('about_course', args=[course.id]))
try: try:
student_module_cache = StudentModuleCache.cache_for_descriptor_descendents( student_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
course.id, request.user, course, depth=2) course.id, user, course, depth=2)
# Has this student been in this course before? # Has this student been in this course before?
first_time = student_module_cache.lookup(course_id, 'course', course.location.url()) is None first_time = student_module_cache.lookup(course_id, 'course', course.location.url()) is None
# Load the module for the course # Load the module for the course
course_module = get_module_for_descriptor(request.user, request, course, student_module_cache, course.id) course_module = get_module_for_descriptor(user, request, course, student_module_cache, course.id)
if course_module is None: if course_module is None:
log.warning('If you see this, something went wrong: if we got this' log.warning('If you see this, something went wrong: if we got this'
' far, should have gotten a course module for this user') ' far, should have gotten a course module for this user')
...@@ -288,7 +290,7 @@ def index(request, course_id, chapter=None, section=None, ...@@ -288,7 +290,7 @@ def index(request, course_id, chapter=None, section=None,
chapter_descriptor = course.get_child_by(lambda m: m.url_name == chapter) chapter_descriptor = course.get_child_by(lambda m: m.url_name == chapter)
if chapter_descriptor is not None: if chapter_descriptor is not None:
instance_module = get_instance_module(course_id, request.user, course_module, student_module_cache) instance_module = get_instance_module(course_id, user, course_module, student_module_cache)
save_child_position(course_module, chapter, instance_module) save_child_position(course_module, chapter, instance_module)
else: else:
raise Http404('No chapter descriptor found with name {}'.format(chapter)) raise Http404('No chapter descriptor found with name {}'.format(chapter))
...@@ -307,9 +309,9 @@ def index(request, course_id, chapter=None, section=None, ...@@ -307,9 +309,9 @@ def index(request, course_id, chapter=None, section=None,
# Load all descendants of the section, because we're going to display its # Load all descendants of the section, because we're going to display its
# html, which in general will need all of its children # html, which in general will need all of its children
section_module_cache = StudentModuleCache.cache_for_descriptor_descendents( section_module_cache = StudentModuleCache.cache_for_descriptor_descendents(
course.id, request.user, section_descriptor, depth=None) course.id, user, section_descriptor, depth=None)
section_module = get_module(request.user, request, section_descriptor.location, section_module = get_module(user, request, section_descriptor.location,
section_module_cache, course.id, position=position, depth=None) section_module_cache, course.id, position=position, depth=None)
if section_module is None: if section_module is None:
# User may be trying to be clever and access something # User may be trying to be clever and access something
...@@ -317,12 +319,12 @@ def index(request, course_id, chapter=None, section=None, ...@@ -317,12 +319,12 @@ def index(request, course_id, chapter=None, section=None,
raise Http404 raise Http404
# Save where we are in the chapter # Save where we are in the chapter
instance_module = get_instance_module(course_id, request.user, chapter_module, student_module_cache) instance_module = get_instance_module(course_id, user, chapter_module, student_module_cache)
save_child_position(chapter_module, section, instance_module) save_child_position(chapter_module, section, instance_module)
# check here if this section *is* a timed module. # check here if this section *is* a timed module.
if section_module.category == 'timelimit': if section_module.category == 'timelimit':
timer_context = update_timelimit_module(request.user, course_id, student_module_cache, timer_context = update_timelimit_module(user, course_id, student_module_cache,
section_descriptor, section_module) section_descriptor, section_module)
if 'timer_expiration_duration' in timer_context: if 'timer_expiration_duration' in timer_context:
context.update(timer_context) context.update(timer_context)
...@@ -363,7 +365,7 @@ def index(request, course_id, chapter=None, section=None, ...@@ -363,7 +365,7 @@ def index(request, course_id, chapter=None, section=None,
log.exception("Error in index view: user={user}, course={course}," log.exception("Error in index view: user={user}, course={course},"
" chapter={chapter} section={section}" " chapter={chapter} section={section}"
"position={position}".format( "position={position}".format(
user=request.user, user=user,
course=course, course=course,
chapter=chapter, chapter=chapter,
section=section, section=section,
......
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