From 5388d5d1fcd60723bf14fc4d466eda85c0f253f8 Mon Sep 17 00:00:00 2001 From: Sanford Student Date: Tue, 9 May 2017 16:25:46 -0400 Subject: [PATCH] Revert "Bulk-reads and Request caching in Course Grade Report" This reverts commit 16e9636513cd8ba8065bb4a64e15a360437e99c7. --- common/djangoapps/course_modes/models.py | 13 -- .../course_modes/tests/test_models.py | 5 +- common/djangoapps/request_cache/__init__.py | 10 -- common/djangoapps/request_cache/middleware.py | 55 ++----- common/djangoapps/student/models.py | 32 +--- common/djangoapps/student/roles.py | 32 +--- lms/djangoapps/certificates/models.py | 37 ++--- lms/djangoapps/certificates/tests/tests.py | 9 +- lms/djangoapps/grades/models.py | 30 +--- .../grades/new/course_grade_factory.py | 9 +- lms/djangoapps/grades/tests/test_new.py | 6 +- lms/djangoapps/grades/tests/test_tasks.py | 4 +- .../instructor_task/tasks_helper/grades.py | 145 ++++++------------ .../tests/test_tasks_helper.py | 45 +----- lms/djangoapps/verify_student/models.py | 20 +-- .../core/djangoapps/course_groups/cohorts.py | 72 ++------- openedx/core/djangoapps/credit/models.py | 11 -- .../core/djangoapps/credit/tests/test_api.py | 4 +- .../djangoapps/user_api/course_tag/api.py | 43 ------ .../djangoapps/user_api/partition_schemes.py | 2 +- .../user_api/tests/test_partition_schemes.py | 5 - .../verified_track_content/models.py | 18 +-- 22 files changed, 130 insertions(+), 477 deletions(-) diff --git a/common/djangoapps/course_modes/models.py b/common/djangoapps/course_modes/models.py index e33ee37222..3f17afc622 100644 --- a/common/djangoapps/course_modes/models.py +++ b/common/djangoapps/course_modes/models.py @@ -9,11 +9,8 @@ from config_models.models import ConfigurationModel from django.core.exceptions import ValidationError from django.db import models from django.db.models import Q -from django.dispatch import receiver from django.utils.translation import ugettext_lazy as _ from openedx.core.djangoapps.xmodule_django.models import CourseKeyField -from request_cache.middleware import ns_request_cached, RequestCache - Mode = namedtuple('Mode', [ @@ -144,8 +141,6 @@ class CourseMode(models.Model): DEFAULT_SHOPPINGCART_MODE_SLUG = HONOR DEFAULT_SHOPPINGCART_MODE = Mode(HONOR, _('Honor'), 0, '', 'usd', None, None, None, None) - CACHE_NAMESPACE = u"course_modes.CourseMode.cache." - class Meta(object): unique_together = ('course_id', 'mode_slug', 'currency') @@ -270,7 +265,6 @@ class CourseMode(models.Model): return [mode.to_tuple() for mode in found_course_modes] @classmethod - @ns_request_cached(CACHE_NAMESPACE) def modes_for_course(cls, course_id, include_expired=False, only_selectable=True): """ Returns a list of the non-expired modes for a given course id @@ -672,13 +666,6 @@ class CourseMode(models.Model): ) -@receiver(models.signals.post_save, sender=CourseMode) -@receiver(models.signals.post_delete, sender=CourseMode) -def invalidate_course_mode_cache(sender, **kwargs): # pylint: disable=unused-argument - """Invalidate the cache of course modes. """ - RequestCache.clear_request_cache(name=CourseMode.CACHE_NAMESPACE) - - class CourseModesArchive(models.Model): """ Store the past values of course_mode that a course had in the past. We decided on having diff --git a/common/djangoapps/course_modes/tests/test_models.py b/common/djangoapps/course_modes/tests/test_models.py index 1dd761cd3e..f992e54ebc 100644 --- a/common/djangoapps/course_modes/tests/test_models.py +++ b/common/djangoapps/course_modes/tests/test_models.py @@ -16,7 +16,7 @@ from opaque_keys.edx.locator import CourseLocator import pytz from course_modes.helpers import enrollment_mode_display -from course_modes.models import CourseMode, Mode, invalidate_course_mode_cache +from course_modes.models import CourseMode, Mode from course_modes.tests.factories import CourseModeFactory @@ -31,9 +31,6 @@ class CourseModeModelTest(TestCase): self.course_key = SlashSeparatedCourseKey('Test', 'TestCourse', 'TestCourseRun') CourseMode.objects.all().delete() - def tearDown(self): - invalidate_course_mode_cache(sender=None) - def create_mode( self, mode_slug, diff --git a/common/djangoapps/request_cache/__init__.py b/common/djangoapps/request_cache/__init__.py index 419508fd74..8ef6a4d058 100644 --- a/common/djangoapps/request_cache/__init__.py +++ b/common/djangoapps/request_cache/__init__.py @@ -41,16 +41,6 @@ def get_cache(name): return middleware.RequestCache.get_request_cache(name) -def clear_cache(name): - """ - Clears the request cache named ``name``. - - Arguments: - name (str): The name of the request cache to clear - """ - return middleware.RequestCache.clear_request_cache(name) - - def get_request(): """ Return the current request. diff --git a/common/djangoapps/request_cache/middleware.py b/common/djangoapps/request_cache/middleware.py index f2f3a40fdd..246f490b69 100644 --- a/common/djangoapps/request_cache/middleware.py +++ b/common/djangoapps/request_cache/middleware.py @@ -39,14 +39,11 @@ class RequestCache(object): return crum.get_current_request() @classmethod - def clear_request_cache(cls, name=None): + def clear_request_cache(cls): """ Empty the request cache. """ - if name is None: - REQUEST_CACHE.data = {} - elif REQUEST_CACHE.data.get(name): - REQUEST_CACHE.data[name] = {} + REQUEST_CACHE.data = {} def process_request(self, request): self.clear_request_cache() @@ -85,43 +82,25 @@ def request_cached(f): cache the value it returns, and return that cached value for subsequent calls with the same args/kwargs within a single request """ - return ns_request_cached()(f) - - -def ns_request_cached(namespace=None): - """ - Same as request_cached above, except an optional namespace can be passed in to compartmentalize the cache. - - Arguments: - namespace (string): An optional namespace to use for the cache. Useful if the caller wants to manage - their own sub-cache by, for example, calling RequestCache.clear_request_cache for their own namespace. - """ - def outer_wrapper(f): + def wrapper(*args, **kwargs): """ - Outer wrapper that decorates the given function - - Arguments: - f (func): the function to wrap + Wrapper function to decorate with. """ - def inner_wrapper(*args, **kwargs): - """ - Wrapper function to decorate with. - """ - # Check to see if we have a result in cache. If not, invoke our wrapped - # function. Cache and return the result to the caller. - rcache = RequestCache.get_request_cache(namespace) - rcache = rcache.data if namespace is None else rcache - cache_key = func_call_cache_key(f, *args, **kwargs) + # Check to see if we have a result in cache. If not, invoke our wrapped + # function. Cache and return the result to the caller. + rcache = RequestCache.get_request_cache() + cache_key = func_call_cache_key(f, *args, **kwargs) - if cache_key in rcache: - return rcache.get(cache_key) - else: - result = f(*args, **kwargs) - rcache[cache_key] = result - return result + if cache_key in rcache.data: + return rcache.data.get(cache_key) + else: + result = f(*args, **kwargs) + rcache.data[cache_key] = result - return inner_wrapper - return outer_wrapper + return result + + wrapper.request_cached_contained_func = f + return wrapper def func_call_cache_key(func, *args, **kwargs): diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index d4a38880fe..e02e0d6c55 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -998,9 +998,7 @@ class CourseEnrollment(models.Model): history = HistoricalRecords() # cache key format e.g enrollment...mode = 'honor' - COURSE_ENROLLMENT_CACHE_KEY = u"enrollment.{}.{}.mode" # TODO Can this be removed? It doesn't seem to be used. - - MODE_CACHE_NAMESPACE = u'CourseEnrollment.mode_and_active' + COURSE_ENROLLMENT_CACHE_KEY = u"enrollment.{}.{}.mode" class Meta(object): unique_together = (('user', 'course_id'),) @@ -1699,28 +1697,12 @@ class CourseEnrollment(models.Model): cls._update_enrollment_in_request_cache(user, course_key, enrollment_state) return enrollment_state - @classmethod - def bulk_fetch_enrollment_states(cls, users, course_key): - """ - Bulk pre-fetches the enrollment states for the given users - for the given course. - """ - # before populating the cache with another bulk set of data, - # remove previously cached entries to keep memory usage low. - request_cache.clear_cache(cls.MODE_CACHE_NAMESPACE) - - records = cls.objects.filter(user__in=users, course_id=course_key).select_related('user__id') - cache = cls._get_mode_active_request_cache() - for record in records: - enrollment_state = CourseEnrollmentState(record.mode, record.is_active) - cls._update_enrollment(cache, record.user.id, course_key, enrollment_state) - @classmethod def _get_mode_active_request_cache(cls): """ Returns the request-specific cache for CourseEnrollment """ - return request_cache.get_cache(cls.MODE_CACHE_NAMESPACE) + return request_cache.get_cache('CourseEnrollment.mode_and_active') @classmethod def _get_enrollment_in_request_cache(cls, user, course_key): @@ -1736,15 +1718,7 @@ class CourseEnrollment(models.Model): Updates the cached value for the user's enrollment in the request cache. """ - cls._update_enrollment(cls._get_mode_active_request_cache(), user.id, course_key, enrollment_state) - - @classmethod - def _update_enrollment(cls, cache, user_id, course_key, enrollment_state): - """ - Updates the cached value for the user's enrollment in the - given cache. - """ - cache[(user_id, course_key)] = enrollment_state + cls._get_mode_active_request_cache()[(user.id, course_key)] = enrollment_state @receiver(models.signals.post_save, sender=CourseEnrollment) diff --git a/common/djangoapps/student/roles.py b/common/djangoapps/student/roles.py index 0fd10873c0..e1bf8189ef 100644 --- a/common/djangoapps/student/roles.py +++ b/common/djangoapps/student/roles.py @@ -4,12 +4,10 @@ adding users, removing users, and listing members """ from abc import ABCMeta, abstractmethod -from collections import defaultdict from django.contrib.auth.models import User import logging -from request_cache import get_cache from student.models import CourseAccessRole from openedx.core.djangoapps.xmodule_django.models import CourseKeyField @@ -36,38 +34,14 @@ def register_access_role(cls): return cls -class BulkRoleCache(object): - CACHE_NAMESPACE = u"student.roles.BulkRoleCache" - CACHE_KEY = u'roles_by_user' - - @classmethod - def prefetch(cls, users): - roles_by_user = defaultdict(set) - get_cache(cls.CACHE_NAMESPACE)[cls.CACHE_KEY] = roles_by_user - - for role in CourseAccessRole.objects.filter(user__in=users).select_related('user__id'): - roles_by_user[role.user.id].add(role) - - users_without_roles = filter(lambda u: u.id not in roles_by_user, users) - for user in users_without_roles: - roles_by_user[user.id] = set() - - @classmethod - def get_user_roles(cls, user): - return get_cache(cls.CACHE_NAMESPACE)[cls.CACHE_KEY][user.id] - - class RoleCache(object): """ A cache of the CourseAccessRoles held by a particular user """ def __init__(self, user): - try: - self._roles = BulkRoleCache.get_user_roles(user) - except KeyError: - self._roles = set( - CourseAccessRole.objects.filter(user=user).all() - ) + self._roles = set( + CourseAccessRole.objects.filter(user=user).all() + ) def has_role(self, role, course_id, org): """ diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index 736a648959..4c2a410594 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -493,18 +493,6 @@ def handle_course_cert_awarded(sender, user, course_key, **kwargs): # pylint: d def certificate_status_for_student(student, course_id): - """ - This returns a dictionary with a key for status, and other information. - See certificate_status for more information. - """ - try: - generated_certificate = GeneratedCertificate.objects.get(user=student, course_id=course_id) - except GeneratedCertificate.DoesNotExist: - generated_certificate = None - return certificate_status(generated_certificate) - - -def certificate_status(generated_certificate): ''' This returns a dictionary with a key for status, and other information. The status is one of the following: @@ -539,7 +527,9 @@ def certificate_status(generated_certificate): # the course_modes app is loaded, resulting in a Django deprecation warning. from course_modes.models import CourseMode - if generated_certificate: + try: + generated_certificate = GeneratedCertificate.objects.get( # pylint: disable=no-member + user=student, course_id=course_id) cert_status = { 'status': generated_certificate.status, 'mode': generated_certificate.mode, @@ -549,7 +539,7 @@ def certificate_status(generated_certificate): cert_status['grade'] = generated_certificate.grade if generated_certificate.mode == 'audit': - course_mode_slugs = [mode.slug for mode in CourseMode.modes_for_course(generated_certificate.course_id)] + course_mode_slugs = [mode.slug for mode in CourseMode.modes_for_course(course_id)] # Short term fix to make sure old audit users with certs still see their certs # only do this if there if no honor mode if 'honor' not in course_mode_slugs: @@ -560,24 +550,31 @@ def certificate_status(generated_certificate): cert_status['download_url'] = generated_certificate.download_url return cert_status - else: - return {'status': CertificateStatuses.unavailable, 'mode': GeneratedCertificate.MODES.honor, 'uuid': None} + + except GeneratedCertificate.DoesNotExist: + pass + return {'status': CertificateStatuses.unavailable, 'mode': GeneratedCertificate.MODES.honor, 'uuid': None} -def certificate_info_for_user(user, grade, user_is_whitelisted, user_certificate): +def certificate_info_for_user(user, course_id, grade, user_is_whitelisted=None): """ Returns the certificate info for a user for grade report. """ + if user_is_whitelisted is None: + user_is_whitelisted = CertificateWhitelist.objects.filter( + user=user, course_id=course_id, whitelist=True + ).exists() + certificate_is_delivered = 'N' certificate_type = 'N/A' eligible_for_certificate = 'Y' if (user_is_whitelisted or grade is not None) and user.profile.allow_certificate \ else 'N' - status = certificate_status(user_certificate) - certificate_generated = status['status'] == CertificateStatuses.downloadable + certificate_status = certificate_status_for_student(user, course_id) + certificate_generated = certificate_status['status'] == CertificateStatuses.downloadable if certificate_generated: certificate_is_delivered = 'Y' - certificate_type = status['mode'] + certificate_type = certificate_status['mode'] return [eligible_for_certificate, certificate_is_delivered, certificate_type] diff --git a/lms/djangoapps/certificates/tests/tests.py b/lms/djangoapps/certificates/tests/tests.py index fb82ce92c5..3ca9c5ef9e 100644 --- a/lms/djangoapps/certificates/tests/tests.py +++ b/lms/djangoapps/certificates/tests/tests.py @@ -54,11 +54,11 @@ class CertificatesModelTest(ModuleStoreTestCase, MilestonesTestCaseMixin): Verify that certificate_info_for_user works. """ student = UserFactory() - _ = CourseFactory.create(org='edx', number='verified', display_name='Verified Course') + course = CourseFactory.create(org='edx', number='verified', display_name='Verified Course') student.profile.allow_certificate = allow_certificate student.profile.save() - certificate_info = certificate_info_for_user(student, grade, whitelisted, user_certificate=None) + certificate_info = certificate_info_for_user(student, course.id, grade, whitelisted) self.assertEqual(certificate_info, output) @unpack @@ -81,13 +81,14 @@ class CertificatesModelTest(ModuleStoreTestCase, MilestonesTestCaseMixin): student.profile.allow_certificate = allow_certificate student.profile.save() - certificate = GeneratedCertificateFactory.create( + GeneratedCertificateFactory.create( user=student, course_id=course.id, status=CertificateStatuses.downloadable, mode='honor' ) - certificate_info = certificate_info_for_user(student, grade, whitelisted, certificate) + + certificate_info = certificate_info_for_user(student, course.id, grade, whitelisted) self.assertEqual(certificate_info, output) def test_course_ids_with_certs_for_user(self): diff --git a/lms/djangoapps/grades/models.py b/lms/djangoapps/grades/models.py index 716f330db5..16c9c4fbf0 100644 --- a/lms/djangoapps/grades/models.py +++ b/lms/djangoapps/grades/models.py @@ -25,7 +25,6 @@ from track.event_transaction_utils import get_event_transaction_id, get_event_tr from coursewarehistoryextended.fields import UnsignedBigIntAutoField from opaque_keys.edx.keys import CourseKey, UsageKey from openedx.core.djangoapps.xmodule_django.models import CourseKeyField, UsageKeyField -from request_cache import get_cache from .config import waffle @@ -523,8 +522,6 @@ class PersistentCourseGrade(DeleteGradesMixin, TimeStampedModel): # Information related to course completion passed_timestamp = models.DateTimeField(u'Date learner earned a passing grade', blank=True, null=True) - CACHE_NAMESPACE = u"grades.models.PersistentCourseGrade" - def __unicode__(self): """ Returns a string representation of this model. @@ -538,21 +535,6 @@ class PersistentCourseGrade(DeleteGradesMixin, TimeStampedModel): u"passed timestamp: {}".format(self.passed_timestamp), ]) - @classmethod - def _cache_key(cls, course_id): - return u"grades_cache.{}".format(course_id) - - @classmethod - def prefetch(cls, course_id, users): - """ - Prefetches grades for the given users for the given course. - """ - get_cache(cls.CACHE_NAMESPACE)[cls._cache_key(course_id)] = { - grade.user_id: grade - for grade in - cls.objects.filter(user_id__in=[user.id for user in users], course_id=course_id) - } - @classmethod def read(cls, user_id, course_id): """ @@ -564,17 +546,7 @@ class PersistentCourseGrade(DeleteGradesMixin, TimeStampedModel): Raises PersistentCourseGrade.DoesNotExist if applicable """ - try: - prefetched_grades = get_cache(cls.CACHE_NAMESPACE)[cls._cache_key(course_id)] - try: - return prefetched_grades[user_id] - except KeyError: - # user's grade is not in the prefetched list, so - # assume they have no grade - raise cls.DoesNotExist - except KeyError: - # grades were not prefetched for the course, so fetch it - return cls.objects.get(user_id=user_id, course_id=course_id) + return cls.objects.get(user_id=user_id, course_id=course_id) @classmethod def update_or_create(cls, user_id, course_id, **kwargs): diff --git a/lms/djangoapps/grades/new/course_grade_factory.py b/lms/djangoapps/grades/new/course_grade_factory.py index 716cf13c77..c731f29ef7 100644 --- a/lms/djangoapps/grades/new/course_grade_factory.py +++ b/lms/djangoapps/grades/new/course_grade_factory.py @@ -81,6 +81,7 @@ class CourseGradeFactory(object): users, course=None, collected_block_structure=None, + course_structure=None, course_key=None, force_update=False, ): @@ -98,9 +99,7 @@ class CourseGradeFactory(object): # compute the grade for all students. # 2. Optimization: the collected course_structure is not # retrieved from the data store multiple times. - course_data = CourseData( - user=None, course=course, collected_block_structure=collected_block_structure, course_key=course_key, - ) + course_data = CourseData(None, course, collected_block_structure, course_structure, course_key) for user in users: with dog_stats_api.timer( 'lms.grades.CourseGradeFactory.iter', @@ -108,9 +107,7 @@ class CourseGradeFactory(object): ): try: method = CourseGradeFactory().update if force_update else CourseGradeFactory().create - course_grade = method( - user, course_data.course, course_data.collected_structure, course_key=course_key, - ) + course_grade = method(user, course, course_data.collected_structure, course_structure, course_key) yield self.GradeResult(user, course_grade, None) except Exception as exc: # pylint: disable=broad-except diff --git a/lms/djangoapps/grades/tests/test_new.py b/lms/djangoapps/grades/tests/test_new.py index 99549856d6..56d0caad6e 100644 --- a/lms/djangoapps/grades/tests/test_new.py +++ b/lms/djangoapps/grades/tests/test_new.py @@ -178,10 +178,10 @@ class TestCourseGradeFactory(GradeTestBase): self.assertEqual(course_grade.letter_grade, u'Pass' if expected_pass else None) self.assertEqual(course_grade.percent, 0.5) - with self.assertNumQueries(11), mock_get_score(1, 2): + with self.assertNumQueries(12), mock_get_score(1, 2): _assert_create(expected_pass=True) - with self.assertNumQueries(13), mock_get_score(1, 2): + with self.assertNumQueries(15), mock_get_score(1, 2): grade_factory.update(self.request.user, self.course) with self.assertNumQueries(1): @@ -189,7 +189,7 @@ class TestCourseGradeFactory(GradeTestBase): self._update_grading_policy(passing=0.9) - with self.assertNumQueries(6): + with self.assertNumQueries(8): _assert_create(expected_pass=False) @ddt.data(True, False) diff --git a/lms/djangoapps/grades/tests/test_tasks.py b/lms/djangoapps/grades/tests/test_tasks.py index 0f467db1c0..f803b90877 100644 --- a/lms/djangoapps/grades/tests/test_tasks.py +++ b/lms/djangoapps/grades/tests/test_tasks.py @@ -409,8 +409,8 @@ class ComputeGradesForCourseTest(HasCourseWithProblemsMixin, ModuleStoreTestCase @ddt.data(*xrange(1, 12, 3)) def test_database_calls(self, batch_size): - per_user_queries = 15 * min(batch_size, 6) # No more than 6 due to offset - with self.assertNumQueries(6 + per_user_queries): + per_user_queries = 17 * min(batch_size, 6) # No more than 6 due to offset + with self.assertNumQueries(5 + per_user_queries): with check_mongo_calls(1): compute_grades_for_course_v2.delay( course_key=six.text_type(self.course.id), diff --git a/lms/djangoapps/instructor_task/tasks_helper/grades.py b/lms/djangoapps/instructor_task/tasks_helper/grades.py index 0818852f98..96030fdb28 100644 --- a/lms/djangoapps/instructor_task/tasks_helper/grades.py +++ b/lms/djangoapps/instructor_task/tasks_helper/grades.py @@ -12,19 +12,14 @@ from time import time from instructor_analytics.basic import list_problem_responses from instructor_analytics.csvs import format_dictlist -from certificates.models import CertificateWhitelist, certificate_info_for_user, GeneratedCertificate +from certificates.models import CertificateWhitelist, certificate_info_for_user from courseware.courses import get_course_by_id -from lms.djangoapps.grades.context import grading_context_for_course, grading_context +from lms.djangoapps.grades.context import grading_context_for_course from lms.djangoapps.grades.new.course_grade_factory import CourseGradeFactory -from lms.djangoapps.grades.models import PersistentCourseGrade from lms.djangoapps.teams.models import CourseTeamMembership from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification -from openedx.core.djangoapps.content.block_structure.api import get_course_in_cache -from openedx.core.djangoapps.course_groups.cohorts import get_cohort, is_course_cohorted, bulk_cache_cohorts -from openedx.core.djangoapps.user_api.course_tag.api import BulkCourseTags +from openedx.core.djangoapps.course_groups.cohorts import get_cohort, is_course_cohorted from student.models import CourseEnrollment -from student.roles import BulkRoleCache -from xmodule.modulestore.django import modulestore from xmodule.partitions.partitions_service import PartitionService from xmodule.split_test_module import get_split_user_partitions @@ -35,7 +30,7 @@ from .utils import upload_csv_to_report_store TASK_LOG = logging.getLogger('edx.celery.task') -class _CourseGradeReportContext(object): +class CourseGradeReportContext(object): """ Internal class that provides a common context to use for a single grade report. When a report is parallelized across multiple processes, @@ -62,10 +57,6 @@ class _CourseGradeReportContext(object): def course(self): return get_course_by_id(self.course_id) - @lazy - def course_structure(self): - return get_course_in_cache(self.course_id) - @lazy def course_experiments(self): return get_split_user_partitions(self.course.user_partitions) @@ -84,9 +75,9 @@ class _CourseGradeReportContext(object): Returns an OrderedDict that maps an assignment type to a dict of subsection-headers and average-header. """ - grading_cxt = grading_context(self.course_structure) + grading_context = grading_context_for_course(self.course_id) graded_assignments_map = OrderedDict() - for assignment_type_name, subsection_infos in grading_cxt['all_graded_subsections_by_type'].iteritems(): + for assignment_type_name, subsection_infos in grading_context['all_graded_subsections_by_type'].iteritems(): graded_subsections_map = OrderedDict() for subsection_index, subsection_info in enumerate(subsection_infos, start=1): subsection = subsection_info['subsection_block'] @@ -121,64 +112,17 @@ class _CourseGradeReportContext(object): return self.task_progress.update_task_state(extra_meta={'step': message}) -class _CertificateBulkContext(object): - def __init__(self, context, users): - certificate_whitelist = CertificateWhitelist.objects.filter(course_id=context.course_id, whitelist=True) - self.whitelisted_user_ids = [entry.user_id for entry in certificate_whitelist] - self.certificates_by_user = { - certificate.user.id: certificate - for certificate in - GeneratedCertificate.objects.filter(course_id=context.course_id, user__in=users) - } - - -class _TeamBulkContext(object): - def __init__(self, context, users): - if context.teams_enabled: - self.teams_by_user = { - membership.user.id: membership.team.name - for membership in - CourseTeamMembership.objects.filter(team__course_id=context.course_id, user__in=users) - } - else: - self.teams_by_user = {} - - -class _EnrollmentBulkContext(object): - def __init__(self, context, users): - CourseEnrollment.bulk_fetch_enrollment_states(users, context.course_id) - self.verified_users = [ - verified.user.id for verified in - SoftwareSecurePhotoVerification.verified_query().filter(user__in=users).select_related('user__id') - ] - - -class _CourseGradeBulkContext(object): - def __init__(self, context, users): - self.certs = _CertificateBulkContext(context, users) - self.teams = _TeamBulkContext(context, users) - self.enrollments = _EnrollmentBulkContext(context, users) - bulk_cache_cohorts(context.course_id, users) - BulkRoleCache.prefetch(users) - PersistentCourseGrade.prefetch(context.course_id, users) - BulkCourseTags.prefetch(context.course_id, users) - - class CourseGradeReport(object): """ Class to encapsulate functionality related to generating Grade Reports. """ - # Batch size for chunking the list of enrollees in the course. - USER_BATCH_SIZE = 100 - @classmethod def generate(cls, _xmodule_instance_args, _entry_id, course_id, _task_input, action_name): """ Public method to generate a grade report. """ - with modulestore().bulk_operations(course_id): - context = _CourseGradeReportContext(_xmodule_instance_args, _entry_id, course_id, _task_input, action_name) - return CourseGradeReport()._generate(context) + context = CourseGradeReportContext(_xmodule_instance_args, _entry_id, course_id, _task_input, action_name) + return CourseGradeReport()._generate(context) def _generate(self, context): """ @@ -222,7 +166,6 @@ class CourseGradeReport(object): A generator of batches of (success_rows, error_rows) for this report. """ for users in self._batch_users(context): - users = filter(lambda u: u is not None, users) yield self._rows_for_users(context, users) def _compile(self, context, batched_rows): @@ -268,11 +211,10 @@ class CourseGradeReport(object): """ Returns a generator of batches of users. """ - def grouper(iterable, chunk_size=self.USER_BATCH_SIZE, fillvalue=None): + def grouper(iterable, chunk_size=1, fillvalue=None): args = [iter(iterable)] * chunk_size return izip_longest(*args, fillvalue=fillvalue) users = CourseEnrollment.objects.users_enrolled_in(context.course_id) - users = users.select_related('profile__allow_certificate') return grouper(users) def _user_grade_results(self, course_grade, context): @@ -307,7 +249,7 @@ class CourseGradeReport(object): """ cohort_group_names = [] if context.cohorts_enabled: - group = get_cohort(user, context.course_id, assign=False, use_cached=True) + group = get_cohort(user, context.course_id, assign=False) cohort_group_names.append(group.name if group else '') return cohort_group_names @@ -322,13 +264,20 @@ class CourseGradeReport(object): experiment_group_names.append(group.name if group else '') return experiment_group_names - def _user_team_names(self, user, bulk_teams): + def _user_team_names(self, user, context): """ Returns a list of names of teams in which the given user belongs. """ - return [bulk_teams.teams_by_user.get(user.id, '')] + team_names = [] + if context.teams_enabled: + try: + membership = CourseTeamMembership.objects.get(user=user, team__course_id=context.course_id) + team_names.append(membership.team.name) + except CourseTeamMembership.DoesNotExist: + team_names.append('') + return team_names - def _user_verification_mode(self, user, context, bulk_enrollments): + def _user_verification_mode(self, user, context): """ Returns a list of enrollment-mode and verification-status for the given user. @@ -337,21 +286,19 @@ class CourseGradeReport(object): verification_status = SoftwareSecurePhotoVerification.verification_status_for_user( user, context.course_id, - enrollment_mode, - user_is_verified=user.id in bulk_enrollments.verified_users, + enrollment_mode ) return [enrollment_mode, verification_status] - def _user_certificate_info(self, user, context, course_grade, bulk_certs): + def _user_certificate_info(self, user, context, course_grade, whitelisted_user_ids): """ Returns the course certification information for the given user. """ - is_whitelisted = user.id in bulk_certs.whitelisted_user_ids certificate_info = certificate_info_for_user( user, + context.course_id, course_grade.letter_grade, - is_whitelisted, - bulk_certs.certificates_by_user.get(user.id), + user.id in whitelisted_user_ids ) TASK_LOG.info( u'Student certificate eligibility: %s ' @@ -364,7 +311,7 @@ class CourseGradeReport(object): course_grade.letter_grade, context.course.grade_cutoffs, user.profile.allow_certificate, - is_whitelisted, + user.id in whitelisted_user_ids, ) return certificate_info @@ -372,30 +319,24 @@ class CourseGradeReport(object): """ Returns a list of rows for the given users for this report. """ - with modulestore().bulk_operations(context.course_id): - bulk_context = _CourseGradeBulkContext(context, users) - - success_rows, error_rows = [], [] - for user, course_grade, error in CourseGradeFactory().iter( - users, - course=context.course, - collected_block_structure=context.course_structure, - course_key=context.course_id, - ): - if not course_grade: - # An empty gradeset means we failed to grade a student. - error_rows.append([user.id, user.username, error.message]) - else: - success_rows.append( - [user.id, user.email, user.username] + - self._user_grade_results(course_grade, context) + - self._user_cohort_group_names(user, context) + - self._user_experiment_group_names(user, context) + - self._user_team_names(user, bulk_context.teams) + - self._user_verification_mode(user, context, bulk_context.enrollments) + - self._user_certificate_info(user, context, course_grade, bulk_context.certs) - ) - return success_rows, error_rows + certificate_whitelist = CertificateWhitelist.objects.filter(course_id=context.course_id, whitelist=True) + whitelisted_user_ids = [entry.user_id for entry in certificate_whitelist] + success_rows, error_rows = [], [] + for user, course_grade, error in CourseGradeFactory().iter(users, course_key=context.course_id): + if not course_grade: + # An empty gradeset means we failed to grade a student. + error_rows.append([user.id, user.username, error.message]) + else: + success_rows.append( + [user.id, user.email, user.username] + + self._user_grade_results(course_grade, context) + + self._user_cohort_group_names(user, context) + + self._user_experiment_group_names(user, context) + + self._user_team_names(user, context) + + self._user_verification_mode(user, context) + + self._user_certificate_info(user, context, course_grade, whitelisted_user_ids) + ) + return success_rows, error_rows class ProblemGradeReport(object): diff --git a/lms/djangoapps/instructor_task/tests/test_tasks_helper.py b/lms/djangoapps/instructor_task/tests/test_tasks_helper.py index b2b18e19ab..dd230dd320 100644 --- a/lms/djangoapps/instructor_task/tests/test_tasks_helper.py +++ b/lms/djangoapps/instructor_task/tests/test_tasks_helper.py @@ -34,11 +34,9 @@ from lms.djangoapps.teams.tests.factories import CourseTeamFactory, CourseTeamMe from lms.djangoapps.verify_student.tests.factories import SoftwareSecurePhotoVerificationFactory from openedx.core.djangoapps.course_groups.models import CourseUserGroupPartitionGroup, CohortMembership from openedx.core.djangoapps.course_groups.tests.helpers import CohortFactory -from openedx.core.djangoapps.credit.tests.factories import CreditCourseFactory import openedx.core.djangoapps.user_api.course_tag.api as course_tag_api from openedx.core.djangoapps.user_api.partition_schemes import RandomUserPartitionScheme from openedx.core.djangoapps.util.testing import ContentGroupTestCase, TestConditionalContent -from request_cache.middleware import RequestCache from shoppingcart.models import ( Order, PaidCourseRegistration, CourseRegistrationCode, Invoice, CourseRegistrationCodeInvoiceItem, InvoiceTransaction, Coupon @@ -46,9 +44,8 @@ from shoppingcart.models import ( from student.models import CourseEnrollment, CourseEnrollmentAllowed, ManualEnrollmentAudit, ALLOWEDTOENROLL_TO_ENROLLED from student.tests.factories import CourseEnrollmentFactory, CourseModeFactory, UserFactory from survey.models import SurveyForm, SurveyAnswer -from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase -from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory, check_mongo_calls +from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.partitions.partitions import Group, UserPartition from ..models import ReportStore @@ -324,44 +321,6 @@ class TestInstructorGradeReport(InstructorGradeReportTestCase): result = CourseGradeReport.generate(None, None, self.course.id, None, 'graded') self.assertDictContainsSubset({'attempted': 1, 'succeeded': 1, 'failed': 0}, result) - @ddt.data( - (ModuleStoreEnum.Type.mongo, 4), - (ModuleStoreEnum.Type.split, 3), - ) - @ddt.unpack - def test_query_counts(self, store_type, mongo_count): - with self.store.default_store(store_type): - experiment_group_a = Group(2, u'Expériment Group A') - experiment_group_b = Group(3, u'Expériment Group B') - experiment_partition = UserPartition( - 1, - u'Content Expériment Configuration', - u'Group Configuration for Content Expériments', - [experiment_group_a, experiment_group_b], - scheme_id='random' - ) - course = CourseFactory.create( - cohort_config={'cohorted': True, 'auto_cohort': True, 'auto_cohort_groups': ['cohort 1', 'cohort 2']}, - user_partitions=[experiment_partition], - teams_configuration={ - 'max_size': 2, 'topics': [{'topic-id': 'topic', 'name': 'Topic', 'description': 'A Topic'}] - }, - ) - _ = CreditCourseFactory(course_key=course.id) - - num_users = 5 - for _ in range(num_users): - user = UserFactory.create() - CourseEnrollment.enroll(user, course.id, mode='verified') - SoftwareSecurePhotoVerificationFactory.create(user=user, status='approved') - - RequestCache.clear_request_cache() - - with patch('lms.djangoapps.instructor_task.tasks_helper.runner._get_current_task'): - with check_mongo_calls(mongo_count): - with self.assertNumQueries(41): - CourseGradeReport.generate(None, None, course.id, None, 'graded') - class TestTeamGradeReport(InstructorGradeReportTestCase): """ Test that teams appear correctly in the grade report when it is enabled for the course. """ @@ -1824,7 +1783,7 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase): 'failed': 3, 'skipped': 2 } - with self.assertNumQueries(171): + with self.assertNumQueries(186): self.assertCertificatesGenerated(task_input, expected_results) expected_results = { diff --git a/lms/djangoapps/verify_student/models.py b/lms/djangoapps/verify_student/models.py index 5373347e58..02e953389f 100644 --- a/lms/djangoapps/verify_student/models.py +++ b/lms/djangoapps/verify_student/models.py @@ -210,19 +210,12 @@ class PhotoVerification(StatusModel): This will check for the user's *initial* verification. """ - return cls.verified_query(earliest_allowed_date).filter(user=user).exists() - - @classmethod - def verified_query(cls, earliest_allowed_date=None): - """ - Return a query set for all records with 'approved' state - that are still valid according to the earliest_allowed_date - value or policy settings. - """ return cls.objects.filter( + user=user, status="approved", - created_at__gte=(earliest_allowed_date or cls._earliest_allowed_date()), - ) + created_at__gte=(earliest_allowed_date + or cls._earliest_allowed_date()) + ).exists() @classmethod def verification_valid_or_pending(cls, user, earliest_allowed_date=None, queryset=None): @@ -958,15 +951,14 @@ class SoftwareSecurePhotoVerification(PhotoVerification): return response @classmethod - def verification_status_for_user(cls, user, course_id, user_enrollment_mode, user_is_verified=None): + def verification_status_for_user(cls, user, course_id, user_enrollment_mode): """ Returns the verification status for use in grade report. """ if user_enrollment_mode not in CourseMode.VERIFIED_MODES: return 'N/A' - if user_is_verified is None: - user_is_verified = cls.user_is_verified(user) + user_is_verified = cls.user_is_verified(user) if not user_is_verified: return 'Not ID Verified' diff --git a/openedx/core/djangoapps/course_groups/cohorts.py b/openedx/core/djangoapps/course_groups/cohorts.py index 963934d625..8e69f239e8 100644 --- a/openedx/core/djangoapps/course_groups/cohorts.py +++ b/openedx/core/djangoapps/course_groups/cohorts.py @@ -14,8 +14,7 @@ from django.utils.translation import ugettext as _ from courseware import courses from eventtracking import tracker -import request_cache -from request_cache.middleware import request_cached +from request_cache.middleware import RequestCache, request_cached from student.models import get_user_by_username_or_email from .models import ( @@ -147,45 +146,8 @@ def get_cohorted_commentables(course_key): return ans -COHORT_CACHE_NAMESPACE = u"cohorts.get_cohort" - - -def _cohort_cache_key(user_id, course_key): - """ - Returns the cache key for the given user_id and course_key. - """ - return u"{}.{}".format(user_id, course_key) - - -def bulk_cache_cohorts(course_key, users): - """ - Pre-fetches and caches the cohort assignments for the - given users, for later fast retrieval by get_cohort. - """ - # before populating the cache with another bulk set of data, - # remove previously cached entries to keep memory usage low. - request_cache.clear_cache(COHORT_CACHE_NAMESPACE) - cache = request_cache.get_cache(COHORT_CACHE_NAMESPACE) - - if is_course_cohorted(course_key): - cohorts_by_user = { - membership.user: membership - for membership in - CohortMembership.objects.filter(user__in=users, course_id=course_key).select_related('user__id') - } - for user, membership in cohorts_by_user.iteritems(): - cache[_cohort_cache_key(user.id, course_key)] = membership.course_user_group - uncohorted_users = filter(lambda u: u not in cohorts_by_user, users) - else: - uncohorted_users = users - - for user in uncohorted_users: - cache[_cohort_cache_key(user.id, course_key)] = None - - def get_cohort(user, course_key, assign=True, use_cached=False): - """ - Returns the user's cohort for the specified course. + """Returns the user's cohort for the specified course. The cohort for the user is cached for the duration of a request. Pass use_cached=True to use the cached value instead of fetching from the @@ -204,19 +166,19 @@ def get_cohort(user, course_key, assign=True, use_cached=False): Raises: ValueError if the CourseKey doesn't exist. """ - cache = request_cache.get_cache(COHORT_CACHE_NAMESPACE) - cache_key = _cohort_cache_key(user.id, course_key) + request_cache = RequestCache.get_request_cache() + cache_key = u"cohorts.get_cohort.{}.{}".format(user.id, course_key) - if use_cached and cache_key in cache: - return cache[cache_key] + if use_cached and cache_key in request_cache.data: + return request_cache.data[cache_key] - cache.pop(cache_key, None) + request_cache.data.pop(cache_key, None) # First check whether the course is cohorted (users shouldn't be in a cohort # in non-cohorted courses, but settings can change after course starts) course_cohort_settings = get_course_cohort_settings(course_key) if not course_cohort_settings.is_cohorted: - return cache.setdefault(cache_key, None) + return request_cache.data.setdefault(cache_key, None) # If course is cohorted, check if the user already has a cohort. try: @@ -224,7 +186,7 @@ def get_cohort(user, course_key, assign=True, use_cached=False): course_id=course_key, user_id=user.id, ) - return cache.setdefault(cache_key, membership.course_user_group) + return request_cache.data.setdefault(cache_key, membership.course_user_group) except CohortMembership.DoesNotExist: # Didn't find the group. If we do not want to assign, return here. if not assign: @@ -239,7 +201,7 @@ def get_cohort(user, course_key, assign=True, use_cached=False): user=user, course_user_group=get_random_cohort(course_key) ) - return cache.setdefault(cache_key, membership.course_user_group) + return request_cache.data.setdefault(cache_key, membership.course_user_group) except IntegrityError as integrity_error: # An IntegrityError is raised when multiple workers attempt to # create the same row in one of the cohort model entries: @@ -457,21 +419,21 @@ def get_group_info_for_cohort(cohort, use_cached=False): use_cached=True to use the cached value instead of fetching from the database. """ - cache = request_cache.get_cache(u"cohorts.get_group_info_for_cohort") - cache_key = unicode(cohort.id) + request_cache = RequestCache.get_request_cache() + cache_key = u"cohorts.get_group_info_for_cohort.{}".format(cohort.id) - if use_cached and cache_key in cache: - return cache[cache_key] + if use_cached and cache_key in request_cache.data: + return request_cache.data[cache_key] - cache.pop(cache_key, None) + request_cache.data.pop(cache_key, None) try: partition_group = CourseUserGroupPartitionGroup.objects.get(course_user_group=cohort) - return cache.setdefault(cache_key, (partition_group.group_id, partition_group.partition_id)) + return request_cache.data.setdefault(cache_key, (partition_group.group_id, partition_group.partition_id)) except CourseUserGroupPartitionGroup.DoesNotExist: pass - return cache.setdefault(cache_key, (None, None)) + return request_cache.data.setdefault(cache_key, (None, None)) def set_assignment_type(user_group, assignment_type): diff --git a/openedx/core/djangoapps/credit/models.py b/openedx/core/djangoapps/credit/models.py index a6829f2fbe..67cfb4ee72 100644 --- a/openedx/core/djangoapps/credit/models.py +++ b/openedx/core/djangoapps/credit/models.py @@ -22,7 +22,6 @@ from model_utils.models import TimeStampedModel import pytz from simple_history.models import HistoricalRecords from openedx.core.djangoapps.xmodule_django.models import CourseKeyField -from request_cache.middleware import ns_request_cached, RequestCache CREDIT_PROVIDER_ID_REGEX = r"[a-z,A-Z,0-9,\-]+" @@ -291,8 +290,6 @@ class CreditRequirement(TimeStampedModel): criteria = JSONField() active = models.BooleanField(default=True) - CACHE_NAMESPACE = u"credit.CreditRequirement.cache." - class Meta(object): unique_together = ('namespace', 'name', 'course') ordering = ["order"] @@ -334,7 +331,6 @@ class CreditRequirement(TimeStampedModel): return credit_requirement, created @classmethod - @ns_request_cached(CACHE_NAMESPACE) def get_course_requirements(cls, course_key, namespace=None, name=None): """ Get credit requirements of a given course. @@ -396,13 +392,6 @@ class CreditRequirement(TimeStampedModel): return None -@receiver(models.signals.post_save, sender=CreditRequirement) -@receiver(models.signals.post_delete, sender=CreditRequirement) -def invalidate_credit_requirement_cache(sender, **kwargs): # pylint: disable=unused-argument - """Invalidate the cache of credit requirements. """ - RequestCache.clear_request_cache(name=CreditRequirement.CACHE_NAMESPACE) - - class CreditRequirementStatus(TimeStampedModel): """ This model represents the status of each requirement. diff --git a/openedx/core/djangoapps/credit/tests/test_api.py b/openedx/core/djangoapps/credit/tests/test_api.py index ee57fbe99c..ac61d1c921 100644 --- a/openedx/core/djangoapps/credit/tests/test_api.py +++ b/openedx/core/djangoapps/credit/tests/test_api.py @@ -664,7 +664,7 @@ class CreditRequirementApiTests(CreditApiTestBase): self.assertFalse(api.is_user_eligible_for_credit(user.username, self.course_key)) # Satisfy the other requirement - with self.assertNumQueries(24): + with self.assertNumQueries(25): api.set_credit_requirement_status( user, self.course_key, @@ -718,7 +718,7 @@ class CreditRequirementApiTests(CreditApiTestBase): # Delete the eligibility entries and satisfy the user's eligibility # requirement again to trigger eligibility notification CreditEligibility.objects.all().delete() - with self.assertNumQueries(16): + with self.assertNumQueries(17): api.set_credit_requirement_status( user, self.course_key, diff --git a/openedx/core/djangoapps/user_api/course_tag/api.py b/openedx/core/djangoapps/user_api/course_tag/api.py index 218954faf3..6aa9d7d948 100644 --- a/openedx/core/djangoapps/user_api/course_tag/api.py +++ b/openedx/core/djangoapps/user_api/course_tag/api.py @@ -7,8 +7,6 @@ Stores global metadata using the UserPreference model, and per-course metadata u UserCourseTag model. """ -from collections import defaultdict -from request_cache import get_cache from ..models import UserCourseTag # Scopes @@ -17,42 +15,6 @@ from ..models import UserCourseTag COURSE_SCOPE = 'course' -class BulkCourseTags(object): - CACHE_NAMESPACE = u'user_api.course_tag.api' - - @classmethod - def prefetch(cls, course_id, users): - """ - Prefetches the value of the course tags for the specified users - for the specified course_id. - - Args: - users: iterator of User objects - course_id: course identifier (CourseKey) - - Returns: - course_tags: a dict of dicts, - where the primary key is the user's id - and the secondary key is the course tag's key - """ - course_tags = defaultdict(dict) - for tag in UserCourseTag.objects.filter(user__in=users, course_id=course_id).select_related('user__id'): - course_tags[tag.user.id][tag.key] = tag.value - get_cache(cls.CACHE_NAMESPACE)[cls._cache_key(course_id)] = course_tags - - @classmethod - def get_course_tag(cls, user_id, course_id, key): - return get_cache(cls.CACHE_NAMESPACE)[cls._cache_key(course_id)][user_id][key] - - @classmethod - def is_prefetched(cls, course_id): - return cls._cache_key(course_id) in get_cache(cls.CACHE_NAMESPACE) - - @classmethod - def _cache_key(cls, course_id): - return u'course_tag.{}'.format(course_id) - - def get_course_tag(user, course_id, key): """ Gets the value of the user's course tag for the specified key in the specified @@ -66,11 +28,6 @@ def get_course_tag(user, course_id, key): Returns: string value, or None if there is no value saved """ - if BulkCourseTags.is_prefetched(course_id): - try: - return BulkCourseTags.get_course_tag(user.id, course_id, key) - except KeyError: - return None try: record = UserCourseTag.objects.get( user=user, diff --git a/openedx/core/djangoapps/user_api/partition_schemes.py b/openedx/core/djangoapps/user_api/partition_schemes.py index 070acbd0cc..a772a1260b 100644 --- a/openedx/core/djangoapps/user_api/partition_schemes.py +++ b/openedx/core/djangoapps/user_api/partition_schemes.py @@ -70,7 +70,7 @@ class RandomUserPartitionScheme(object): exc_info=True ) - if group is None and assign and not course_tag_api.BulkCourseTags.is_prefetched(course_key): + if group is None and assign: if not user_partition.groups: raise UserPartitionError('Cannot assign user to an empty user partition') diff --git a/openedx/core/djangoapps/user_api/tests/test_partition_schemes.py b/openedx/core/djangoapps/user_api/tests/test_partition_schemes.py index d12bc3f8c6..ba80846cea 100644 --- a/openedx/core/djangoapps/user_api/tests/test_partition_schemes.py +++ b/openedx/core/djangoapps/user_api/tests/test_partition_schemes.py @@ -26,11 +26,6 @@ class MemoryCourseTagAPI(object): """Gets the value of ``key``""" self._tags[course_id][key] = value - class BulkCourseTags(object): - @classmethod - def is_prefetched(self, course_id): - return False - class TestRandomUserPartitionScheme(PartitionTestCase): """ diff --git a/openedx/core/djangoapps/verified_track_content/models.py b/openedx/core/djangoapps/verified_track_content/models.py index 8c96960261..213c53bf5b 100644 --- a/openedx/core/djangoapps/verified_track_content/models.py +++ b/openedx/core/djangoapps/verified_track_content/models.py @@ -5,17 +5,17 @@ from django.db import models from django.utils.translation import ugettext_lazy from django.dispatch import receiver from django.db.models.signals import post_save, pre_save -import logging -from lms.djangoapps.courseware.courses import get_course_by_id from openedx.core.djangoapps.xmodule_django.models import CourseKeyField +from student.models import CourseEnrollment +from lms.djangoapps.courseware.courses import get_course_by_id + from openedx.core.djangoapps.verified_track_content.tasks import sync_cohort_with_mode from openedx.core.djangoapps.course_groups.cohorts import ( get_course_cohorts, CourseCohort, is_course_cohorted, get_random_cohort ) -from request_cache.middleware import ns_request_cached, RequestCache -from student.models import CourseEnrollment +import logging log = logging.getLogger(__name__) @@ -97,8 +97,6 @@ class VerifiedTrackCohortedCourse(models.Model): enabled = models.BooleanField() - CACHE_NAMESPACE = u"verified_track_content.VerifiedTrackCohortedCourse.cache." - def __unicode__(self): return u"Course: {}, enabled: {}".format(unicode(self.course_key), self.enabled) @@ -121,7 +119,6 @@ class VerifiedTrackCohortedCourse(models.Model): return None @classmethod - @ns_request_cached(CACHE_NAMESPACE) def is_verified_track_cohort_enabled(cls, course_key): """ Checks whether or not verified track cohort is enabled for the given course. @@ -137,10 +134,3 @@ class VerifiedTrackCohortedCourse(models.Model): return cls.objects.get(course_key=course_key).enabled except cls.DoesNotExist: return False - - -@receiver(models.signals.post_save, sender=VerifiedTrackCohortedCourse) -@receiver(models.signals.post_delete, sender=VerifiedTrackCohortedCourse) -def invalidate_verified_track_cache(sender, **kwargs): # pylint: disable=unused-argument - """Invalidate the cache of VerifiedTrackCohortedCourse. """ - RequestCache.clear_request_cache(name=VerifiedTrackCohortedCourse.CACHE_NAMESPACE)