Commit 390e2c18 by Ben McMorran

Merge pull request #8528 from edx/benmcmorran/discussion-caching

TNL-2291 Discussion forums permission caching
parents 89f13e15 b92ad0ad
...@@ -6,6 +6,7 @@ from django.contrib.auth.models import User ...@@ -6,6 +6,7 @@ from django.contrib.auth.models import User
from django.dispatch import receiver from django.dispatch import receiver
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.utils.translation import ugettext_noop from django.utils.translation import ugettext_noop
from student.models import CourseEnrollment from student.models import CourseEnrollment
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
...@@ -84,15 +85,14 @@ class Role(models.Model): ...@@ -84,15 +85,14 @@ class Role(models.Model):
self.permissions.add(Permission.objects.get_or_create(name=permission)[0]) self.permissions.add(Permission.objects.get_or_create(name=permission)[0])
def has_permission(self, permission): def has_permission(self, permission):
"""Returns True if this role has the given permission, False otherwise."""
course = modulestore().get_course(self.course_id) course = modulestore().get_course(self.course_id)
if course is None: if course is None:
raise ItemNotFoundError(self.course_id) raise ItemNotFoundError(self.course_id)
if self.name == FORUM_ROLE_STUDENT and \ if permission_blacked_out(course, {self.name}, permission):
(permission.startswith('edit') or permission.startswith('update') or permission.startswith('create')) and \
(not course.forum_posts_allowed):
return False return False
return self.permissions.filter(name=permission).exists() return self.permissions.filter(name=permission).exists() # pylint: disable=no-member
class Permission(models.Model): class Permission(models.Model):
...@@ -105,3 +105,35 @@ class Permission(models.Model): ...@@ -105,3 +105,35 @@ class Permission(models.Model):
def __unicode__(self): def __unicode__(self):
return self.name return self.name
def permission_blacked_out(course, role_names, permission_name):
"""Returns true if a user in course with the given roles would have permission_name blacked out.
This will return true if it is a permission that the user might have normally had for the course, but does not have
right this moment because we are in a discussion blackout period (as defined by the settings on the course module).
Namely, they can still view, but they can't edit, update, or create anything. This only applies to students, as
moderators of any kind still have posting privileges during discussion blackouts.
"""
return (
not course.forum_posts_allowed and
role_names == {FORUM_ROLE_STUDENT} and
any([permission_name.startswith(prefix) for prefix in ['edit', 'update', 'create']])
)
def all_permissions_for_user_in_course(user, course_id): # pylint: disable=invalid-name
"""Returns all the permissions the user has in the given course."""
course = modulestore().get_course(course_id)
if course is None:
raise ItemNotFoundError(course_id)
all_roles = {role.name for role in Role.objects.filter(users=user, course_id=course_id)}
permissions = {
permission.name
for permission
in Permission.objects.filter(roles__users=user, roles__course_id=course_id)
if not permission_blacked_out(course, all_roles, permission.name)
}
return permissions
...@@ -1215,6 +1215,7 @@ class MetricsMixin(object): ...@@ -1215,6 +1215,7 @@ class MetricsMixin(object):
finally: finally:
end_time = time.time() end_time = time.time()
duration = end_time - start_time
course_id = getattr(self, 'course_id', '') course_id = getattr(self, 'course_id', '')
tags = [ tags = [
u'view_name:{}'.format(view_name), u'view_name:{}'.format(view_name),
...@@ -1227,10 +1228,17 @@ class MetricsMixin(object): ...@@ -1227,10 +1228,17 @@ class MetricsMixin(object):
dog_stats_api.increment(XMODULE_METRIC_NAME, tags=tags, sample_rate=XMODULE_METRIC_SAMPLE_RATE) dog_stats_api.increment(XMODULE_METRIC_NAME, tags=tags, sample_rate=XMODULE_METRIC_SAMPLE_RATE)
dog_stats_api.histogram( dog_stats_api.histogram(
XMODULE_DURATION_METRIC_NAME, XMODULE_DURATION_METRIC_NAME,
end_time - start_time, duration,
tags=tags, tags=tags,
sample_rate=XMODULE_METRIC_SAMPLE_RATE, sample_rate=XMODULE_METRIC_SAMPLE_RATE,
) )
log.debug(
"%.3fs - render %s.%s (%s)",
duration,
block.__class__.__name__,
view_name,
getattr(block, 'location', ''),
)
def handle(self, block, handler_name, request, suffix=''): def handle(self, block, handler_name, request, suffix=''):
start_time = time.time() start_time = time.time()
...@@ -1244,6 +1252,7 @@ class MetricsMixin(object): ...@@ -1244,6 +1252,7 @@ class MetricsMixin(object):
finally: finally:
end_time = time.time() end_time = time.time()
duration = end_time - start_time
course_id = getattr(self, 'course_id', '') course_id = getattr(self, 'course_id', '')
tags = [ tags = [
u'handler_name:{}'.format(handler_name), u'handler_name:{}'.format(handler_name),
...@@ -1256,10 +1265,17 @@ class MetricsMixin(object): ...@@ -1256,10 +1265,17 @@ class MetricsMixin(object):
dog_stats_api.increment(XMODULE_METRIC_NAME, tags=tags, sample_rate=XMODULE_METRIC_SAMPLE_RATE) dog_stats_api.increment(XMODULE_METRIC_NAME, tags=tags, sample_rate=XMODULE_METRIC_SAMPLE_RATE)
dog_stats_api.histogram( dog_stats_api.histogram(
XMODULE_DURATION_METRIC_NAME, XMODULE_DURATION_METRIC_NAME,
end_time - start_time, duration,
tags=tags, tags=tags,
sample_rate=XMODULE_METRIC_SAMPLE_RATE sample_rate=XMODULE_METRIC_SAMPLE_RATE
) )
log.debug(
"%.3fs - handle %s.%s (%s)",
duration,
block.__class__.__name__,
handler_name,
getattr(block, 'location', ''),
)
class DescriptorSystem(MetricsMixin, ConfigurableFragmentWrapper, Runtime): # pylint: disable=abstract-method class DescriptorSystem(MetricsMixin, ConfigurableFragmentWrapper, Runtime): # pylint: disable=abstract-method
......
...@@ -29,7 +29,7 @@ from django_comment_client.utils import ( ...@@ -29,7 +29,7 @@ from django_comment_client.utils import (
get_discussion_categories_ids, get_discussion_categories_ids,
get_discussion_id_map, get_discussion_id_map,
) )
from django_comment_client.permissions import check_permissions_by_view, cached_has_permission from django_comment_client.permissions import check_permissions_by_view, has_permission
from eventtracking import tracker from eventtracking import tracker
import lms.lib.comment_client as cc import lms.lib.comment_client as cc
...@@ -490,7 +490,10 @@ def un_flag_abuse_for_thread(request, course_id, thread_id): ...@@ -490,7 +490,10 @@ def un_flag_abuse_for_thread(request, course_id, thread_id):
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
course = get_course_by_id(course_key) course = get_course_by_id(course_key)
thread = cc.Thread.find(thread_id) thread = cc.Thread.find(thread_id)
remove_all = cached_has_permission(request.user, 'openclose_thread', course_key) or has_access(request.user, 'staff', course) remove_all = (
has_permission(request.user, 'openclose_thread', course_key) or
has_access(request.user, 'staff', course)
)
thread.unFlagAbuse(user, thread, remove_all) thread.unFlagAbuse(user, thread, remove_all)
return JsonResponse(prepare_content(thread.to_dict(), course_key)) return JsonResponse(prepare_content(thread.to_dict(), course_key))
...@@ -522,7 +525,10 @@ def un_flag_abuse_for_comment(request, course_id, comment_id): ...@@ -522,7 +525,10 @@ def un_flag_abuse_for_comment(request, course_id, comment_id):
user = cc.User.from_django_user(request.user) user = cc.User.from_django_user(request.user)
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
course = get_course_by_id(course_key) course = get_course_by_id(course_key)
remove_all = cached_has_permission(request.user, 'openclose_thread', course_key) or has_access(request.user, 'staff', course) remove_all = (
has_permission(request.user, 'openclose_thread', course_key) or
has_access(request.user, 'staff', course)
)
comment = cc.Comment.find(comment_id) comment = cc.Comment.find(comment_id)
comment.unFlagAbuse(user, comment, remove_all) comment.unFlagAbuse(user, comment, remove_all)
return JsonResponse(prepare_content(comment.to_dict(), course_key)) return JsonResponse(prepare_content(comment.to_dict(), course_key))
......
...@@ -316,12 +316,12 @@ class SingleThreadQueryCountTestCase(ModuleStoreTestCase): ...@@ -316,12 +316,12 @@ class SingleThreadQueryCountTestCase(ModuleStoreTestCase):
MODULESTORE = TEST_DATA_MONGO_MODULESTORE MODULESTORE = TEST_DATA_MONGO_MODULESTORE
@ddt.data( @ddt.data(
# old mongo with cache: 15 # old mongo with cache
(ModuleStoreEnum.Type.mongo, 1, 21, 15, 40, 27), (ModuleStoreEnum.Type.mongo, 1, 7, 5, 12, 7),
(ModuleStoreEnum.Type.mongo, 50, 315, 15, 628, 27), (ModuleStoreEnum.Type.mongo, 50, 7, 5, 12, 7),
# split mongo: 3 queries, regardless of thread response size. # split mongo: 3 queries, regardless of thread response size.
(ModuleStoreEnum.Type.split, 1, 3, 3, 40, 27), (ModuleStoreEnum.Type.split, 1, 3, 3, 12, 7),
(ModuleStoreEnum.Type.split, 50, 3, 3, 628, 27), (ModuleStoreEnum.Type.split, 50, 3, 3, 12, 7),
) )
@ddt.unpack @ddt.unpack
def test_number_of_mongo_queries( def test_number_of_mongo_queries(
...@@ -363,27 +363,15 @@ class SingleThreadQueryCountTestCase(ModuleStoreTestCase): ...@@ -363,27 +363,15 @@ class SingleThreadQueryCountTestCase(ModuleStoreTestCase):
self.assertEquals(response.status_code, 200) self.assertEquals(response.status_code, 200)
self.assertEquals(len(json.loads(response.content)["content"]["children"]), num_thread_responses) self.assertEquals(len(json.loads(response.content)["content"]["children"]), num_thread_responses)
# TODO: update this once django cache is disabled in tests # Test uncached first, then cached now that the cache is warm.
# Test with and without cache, clearing before and after use.
single_thread_local_cache = cache.get_cache(
backend='default',
LOCATION='single_thread_local_cache'
)
single_thread_dummy_cache = cache.get_cache(
backend='django.core.cache.backends.dummy.DummyCache',
LOCATION='single_thread_local_cache'
)
cached_calls = [ cached_calls = [
[single_thread_dummy_cache, num_uncached_mongo_calls, num_uncached_sql_queries], [num_uncached_mongo_calls, num_uncached_sql_queries],
[single_thread_local_cache, num_cached_mongo_calls, num_cached_sql_queries] [num_cached_mongo_calls, num_cached_sql_queries],
] ]
for single_thread_cache, expected_mongo_calls, expected_sql_queries in cached_calls: for expected_mongo_calls, expected_sql_queries in cached_calls:
single_thread_cache.clear() with self.assertNumQueries(expected_sql_queries):
with patch("django_comment_client.permissions.CACHE", single_thread_cache): with check_mongo_calls(expected_mongo_calls):
with self.assertNumQueries(expected_sql_queries): call_single_thread()
with check_mongo_calls(expected_mongo_calls):
call_single_thread()
single_thread_cache.clear()
@patch('requests.request') @patch('requests.request')
......
...@@ -30,7 +30,7 @@ from courseware.access import has_access ...@@ -30,7 +30,7 @@ from courseware.access import has_access
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from ccx.overrides import get_current_ccx from ccx.overrides import get_current_ccx
from django_comment_client.permissions import cached_has_permission from django_comment_client.permissions import has_permission
from django_comment_client.utils import ( from django_comment_client.utils import (
merge_dict, merge_dict,
extract, extract,
...@@ -209,7 +209,7 @@ def inline_discussion(request, course_key, discussion_id): ...@@ -209,7 +209,7 @@ def inline_discussion(request, course_key, discussion_id):
with newrelic.agent.FunctionTrace(nr_transaction, "get_metadata_for_threads"): with newrelic.agent.FunctionTrace(nr_transaction, "get_metadata_for_threads"):
annotated_content_info = utils.get_metadata_for_threads(course_key, threads, request.user, user_info) annotated_content_info = utils.get_metadata_for_threads(course_key, threads, request.user, user_info)
is_staff = cached_has_permission(request.user, 'openclose_thread', course.id) is_staff = has_permission(request.user, 'openclose_thread', course.id)
threads = [utils.prepare_content(thread, course_key, is_staff) for thread in threads] threads = [utils.prepare_content(thread, course_key, is_staff) for thread in threads]
with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"): with newrelic.agent.FunctionTrace(nr_transaction, "add_courseware_context"):
add_courseware_context(threads, course, request.user) add_courseware_context(threads, course, request.user)
...@@ -241,7 +241,7 @@ def forum_form_discussion(request, course_key): ...@@ -241,7 +241,7 @@ def forum_form_discussion(request, course_key):
try: try:
unsafethreads, query_params = get_threads(request, course) # This might process a search query unsafethreads, query_params = get_threads(request, course) # This might process a search query
is_staff = cached_has_permission(request.user, 'openclose_thread', course.id) is_staff = has_permission(request.user, 'openclose_thread', course.id)
threads = [utils.prepare_content(thread, course_key, is_staff) for thread in unsafethreads] threads = [utils.prepare_content(thread, course_key, is_staff) for thread in unsafethreads]
except cc.utils.CommentClientMaintenanceError: except cc.utils.CommentClientMaintenanceError:
log.warning("Forum is in maintenance mode") log.warning("Forum is in maintenance mode")
...@@ -275,11 +275,14 @@ def forum_form_discussion(request, course_key): ...@@ -275,11 +275,14 @@ def forum_form_discussion(request, course_key):
'threads': _attr_safe_json(threads), 'threads': _attr_safe_json(threads),
'thread_pages': query_params['num_pages'], 'thread_pages': query_params['num_pages'],
'user_info': _attr_safe_json(user_info), 'user_info': _attr_safe_json(user_info),
'flag_moderator': cached_has_permission(request.user, 'openclose_thread', course.id) or has_access(request.user, 'staff', course), 'flag_moderator': (
has_permission(request.user, 'openclose_thread', course.id) or
has_access(request.user, 'staff', course)
),
'annotated_content_info': _attr_safe_json(annotated_content_info), 'annotated_content_info': _attr_safe_json(annotated_content_info),
'course_id': course.id.to_deprecated_string(), 'course_id': course.id.to_deprecated_string(),
'roles': _attr_safe_json(utils.get_role_ids(course_key)), 'roles': _attr_safe_json(utils.get_role_ids(course_key)),
'is_moderator': cached_has_permission(request.user, "see_all_cohorts", course_key), 'is_moderator': has_permission(request.user, "see_all_cohorts", course_key),
'cohorts': course_settings["cohorts"], # still needed to render _thread_list_template 'cohorts': course_settings["cohorts"], # still needed to render _thread_list_template
'user_cohort': user_cohort_id, # read from container in NewPostView 'user_cohort': user_cohort_id, # read from container in NewPostView
'is_course_cohorted': is_course_cohorted(course_key), # still needed to render _thread_list_template 'is_course_cohorted': is_course_cohorted(course_key), # still needed to render _thread_list_template
...@@ -304,7 +307,7 @@ def single_thread(request, course_key, discussion_id, thread_id): ...@@ -304,7 +307,7 @@ def single_thread(request, course_key, discussion_id, thread_id):
course_settings = make_course_settings(course, request.user) course_settings = make_course_settings(course, request.user)
cc_user = cc.User.from_django_user(request.user) cc_user = cc.User.from_django_user(request.user)
user_info = cc_user.to_dict() user_info = cc_user.to_dict()
is_moderator = cached_has_permission(request.user, "see_all_cohorts", course_key) is_moderator = has_permission(request.user, "see_all_cohorts", course_key)
# Verify that the student has access to this thread if belongs to a discussion module # Verify that the student has access to this thread if belongs to a discussion module
if discussion_id not in utils.get_discussion_categories_ids(course, request.user): if discussion_id not in utils.get_discussion_categories_ids(course, request.user):
...@@ -331,7 +334,7 @@ def single_thread(request, course_key, discussion_id, thread_id): ...@@ -331,7 +334,7 @@ def single_thread(request, course_key, discussion_id, thread_id):
if getattr(thread, "group_id", None) is not None and user_group_id != thread.group_id: if getattr(thread, "group_id", None) is not None and user_group_id != thread.group_id:
raise Http404 raise Http404
is_staff = cached_has_permission(request.user, 'openclose_thread', course.id) is_staff = has_permission(request.user, 'openclose_thread', course.id)
if request.is_ajax(): if request.is_ajax():
with newrelic.agent.FunctionTrace(nr_transaction, "get_annotated_content_infos"): with newrelic.agent.FunctionTrace(nr_transaction, "get_annotated_content_infos"):
annotated_content_info = utils.get_annotated_content_infos(course_key, thread, request.user, user_info=user_info) annotated_content_info = utils.get_annotated_content_infos(course_key, thread, request.user, user_info=user_info)
...@@ -381,7 +384,10 @@ def single_thread(request, course_key, discussion_id, thread_id): ...@@ -381,7 +384,10 @@ def single_thread(request, course_key, discussion_id, thread_id):
'is_moderator': is_moderator, 'is_moderator': is_moderator,
'thread_pages': query_params['num_pages'], 'thread_pages': query_params['num_pages'],
'is_course_cohorted': is_course_cohorted(course_key), 'is_course_cohorted': is_course_cohorted(course_key),
'flag_moderator': cached_has_permission(request.user, 'openclose_thread', course.id) or has_access(request.user, 'staff', course), 'flag_moderator': (
has_permission(request.user, 'openclose_thread', course.id) or
has_access(request.user, 'staff', course)
),
'cohorts': course_settings["cohorts"], 'cohorts': course_settings["cohorts"],
'user_cohort': user_cohort, 'user_cohort': user_cohort,
'sort_preference': cc_user.default_sort_key, 'sort_preference': cc_user.default_sort_key,
...@@ -428,7 +434,7 @@ def user_profile(request, course_key, user_id): ...@@ -428,7 +434,7 @@ def user_profile(request, course_key, user_id):
with newrelic.agent.FunctionTrace(nr_transaction, "get_metadata_for_threads"): with newrelic.agent.FunctionTrace(nr_transaction, "get_metadata_for_threads"):
annotated_content_info = utils.get_metadata_for_threads(course_key, threads, request.user, user_info) annotated_content_info = utils.get_metadata_for_threads(course_key, threads, request.user, user_info)
is_staff = cached_has_permission(request.user, 'openclose_thread', course.id) is_staff = has_permission(request.user, 'openclose_thread', course.id)
threads = [utils.prepare_content(thread, course_key, is_staff) for thread in threads] threads = [utils.prepare_content(thread, course_key, is_staff) for thread in threads]
if request.is_ajax(): if request.is_ajax():
return utils.JsonResponse({ return utils.JsonResponse({
...@@ -509,7 +515,7 @@ def followed_threads(request, course_key, user_id): ...@@ -509,7 +515,7 @@ def followed_threads(request, course_key, user_id):
with newrelic.agent.FunctionTrace(nr_transaction, "get_metadata_for_threads"): with newrelic.agent.FunctionTrace(nr_transaction, "get_metadata_for_threads"):
annotated_content_info = utils.get_metadata_for_threads(course_key, threads, request.user, user_info) annotated_content_info = utils.get_metadata_for_threads(course_key, threads, request.user, user_info)
if request.is_ajax(): if request.is_ajax():
is_staff = cached_has_permission(request.user, 'openclose_thread', course.id) is_staff = has_permission(request.user, 'openclose_thread', course.id)
return utils.JsonResponse({ return utils.JsonResponse({
'annotated_content_info': annotated_content_info, 'annotated_content_info': annotated_content_info,
'discussion_data': [utils.prepare_content(thread, course_key, is_staff) for thread in threads], 'discussion_data': [utils.prepare_content(thread, course_key, is_staff) for thread in threads],
......
...@@ -5,34 +5,27 @@ Module for checking permissions with the comment_client backend ...@@ -5,34 +5,27 @@ Module for checking permissions with the comment_client backend
import logging import logging
from types import NoneType from types import NoneType
from django.core import cache from django.core import cache
from request_cache.middleware import RequestCache
from lms.lib.comment_client import Thread from lms.lib.comment_client import Thread
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
CACHE = cache.get_cache('default') from django_comment_common.models import all_permissions_for_user_in_course
CACHE_LIFESPAN = 60
def cached_has_permission(user, permission, course_id=None):
"""
Call has_permission if it's not cached. A change in a user's role or
a role's permissions will only become effective after CACHE_LIFESPAN seconds.
"""
assert isinstance(course_id, (NoneType, CourseKey))
key = u"permission_{user_id:d}_{course_id}_{permission}".format(
user_id=user.id, course_id=course_id, permission=permission)
val = CACHE.get(key, None)
if val not in [True, False]:
val = has_permission(user, permission, course_id=course_id)
CACHE.set(key, val, CACHE_LIFESPAN)
return val
def has_permission(user, permission, course_id=None): def has_permission(user, permission, course_id=None):
assert isinstance(course_id, (NoneType, CourseKey)) assert isinstance(course_id, (NoneType, CourseKey))
for role in user.roles.filter(course_id=course_id): request_cache_dict = RequestCache.get_request_cache().data
if role.has_permission(permission): cache_key = "django_comment_client.permissions.has_permission.all_permissions.{}.{}".format(
return True user.id, course_id
return False )
if cache_key in request_cache_dict:
all_permissions = request_cache_dict[cache_key]
else:
all_permissions = all_permissions_for_user_in_course(user, course_id)
request_cache_dict[cache_key] = all_permissions
return permission in all_permissions
CONDITIONS = ['is_open', 'is_author', 'is_question_author'] CONDITIONS = ['is_open', 'is_author', 'is_question_author']
...@@ -84,7 +77,7 @@ def _check_conditions_permissions(user, permissions, course_id, content): ...@@ -84,7 +77,7 @@ def _check_conditions_permissions(user, permissions, course_id, content):
if isinstance(per, basestring): if isinstance(per, basestring):
if per in CONDITIONS: if per in CONDITIONS:
return _check_condition(user, per, content) return _check_condition(user, per, content)
return cached_has_permission(user, per, course_id=course_id) return has_permission(user, per, course_id=course_id)
elif isinstance(per, list) and operator in ["and", "or"]: elif isinstance(per, list) and operator in ["and", "or"]:
results = [test(user, x, operator="and") for x in per] results = [test(user, x, operator="and") for x in per]
if operator == "or": if operator == "or":
......
...@@ -15,7 +15,7 @@ from opaque_keys.edx.keys import CourseKey ...@@ -15,7 +15,7 @@ from opaque_keys.edx.keys import CourseKey
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from django_comment_common.models import Role, FORUM_ROLE_STUDENT from django_comment_common.models import Role, FORUM_ROLE_STUDENT
from django_comment_client.permissions import check_permissions_by_view, cached_has_permission from django_comment_client.permissions import check_permissions_by_view, has_permission
from edxmako import lookup_template from edxmako import lookup_template
from courseware.access import has_access from courseware.access import has_access
...@@ -506,8 +506,8 @@ def prepare_content(content, course_key, is_staff=False, course_is_cohorted=None ...@@ -506,8 +506,8 @@ def prepare_content(content, course_key, is_staff=False, course_is_cohorted=None
# Only reveal endorser if requester can see author or if endorser is staff # Only reveal endorser if requester can see author or if endorser is staff
if ( if (
endorser and endorser and
("username" in fields or cached_has_permission(endorser, "endorse_comment", course_key)) ("username" in fields or has_permission(endorser, "endorse_comment", course_key))
): ):
endorsement["username"] = endorser.username endorsement["username"] = endorser.username
else: else:
...@@ -552,7 +552,7 @@ def get_group_id_for_comments_service(request, course_key, commentable_id=None): ...@@ -552,7 +552,7 @@ def get_group_id_for_comments_service(request, course_key, commentable_id=None):
requested_group_id = request.GET.get('group_id') requested_group_id = request.GET.get('group_id')
elif request.method == "POST": elif request.method == "POST":
requested_group_id = request.POST.get('group_id') requested_group_id = request.POST.get('group_id')
if cached_has_permission(request.user, "see_all_cohorts", course_key): if has_permission(request.user, "see_all_cohorts", course_key):
if not requested_group_id: if not requested_group_id:
return None return None
try: try:
......
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