def _update_cache(cls, user_id, course_key, visible_blocks): """ Adds a specific set of visible blocks to the request cache. This assumes that prefetch has already been called. """ get_cache(cls._CACHE_NAMESPACE)[cls._cache_key(user_id, course_key)].update( {visible_block.hashed: visible_block for visible_block in visible_blocks} )
def prefetch(cls, course_key, users): """ Prefetches grades for the given users in the given course. """ cache_key = cls._cache_key(course_key) get_cache(cls._CACHE_NAMESPACE)[cache_key] = defaultdict(list) cached_grades = get_cache(cls._CACHE_NAMESPACE)[cache_key] queryset = cls.objects.select_related('visible_blocks', 'override').filter( user_id__in=[user.id for user in users], course_id=course_key, ) for record in queryset: cached_grades[record.user_id].append(record)
def __enter__(self): connection = transaction.get_connection(self.using) cache = get_cache(OUTER_ATOMIC_CACHE_NAME) # By default it is enabled. enable = True # If name is set it is only enabled if requested by calling enable_named_outer_atomic(). if self.name: enable = cache.get(self.name, False) if enable: # TestCase setup nests tests in two atomics - one for the test class and one for the individual test. # The outermost atomic starts a transaction - so does not have a savepoint. # The inner atomic starts a savepoint around the test. # So, for tests only, there should be exactly one savepoint_id and two atomic_for_testcase_calls. # atomic_for_testcase_calls below is added in a monkey-patch for tests only. if self.ALLOW_NESTED and (self.atomic_for_testcase_calls - len(connection.savepoint_ids)) < 1: raise transaction.TransactionManagementError('Cannot be inside an atomic block.') # Otherwise, this shouldn't be nested in any atomic block. if not self.ALLOW_NESTED and connection.in_atomic_block: raise transaction.TransactionManagementError('Cannot be inside an atomic block.') # This will set the transaction isolation level to READ COMMITTED for the next transaction. if self.read_committed is True: if connection.vendor == 'mysql': cursor = connection.cursor() cursor.execute("SET TRANSACTION ISOLATION LEVEL READ COMMITTED") super(OuterAtomic, self).__enter__()
def user_timezone_locale_prefs(request): """ Checks if request has an authenticated user. If so, sends set (or none if unset) time_zone and language prefs. This interacts with the DateUtils to either display preferred or attempt to determine system/browser set time_zones and languages """ cached_value = get_cache(CACHE_NAME) if not cached_value: user_prefs = { 'user_timezone': None, 'user_language': None, } if hasattr(request, 'user') and request.user.is_authenticated: try: user_preferences = get_user_preferences(request.user) except (UserNotFound, UserAPIInternalError): cached_value.update(user_prefs) else: user_prefs = { key: user_preferences.get(pref_name, None) for key, pref_name in RETRIEVABLE_PREFERENCES.iteritems() } cached_value.update(user_prefs) return cached_value
def get_course_content_milestones(course_id, content_id=None, relationship='requires', user_id=None): """ Client API operation adapter/wrapper Uses the request cache to store all of a user's milestones Returns all content blocks in a course if content_id is None, otherwise it just returns that specific content block. """ if not settings.FEATURES.get('MILESTONES_APP'): return [] if user_id is None: return milestones_api.get_course_content_milestones(course_id, content_id, relationship) request_cache_dict = get_cache(REQUEST_CACHE_NAME) if user_id not in request_cache_dict: request_cache_dict[user_id] = {} if relationship not in request_cache_dict[user_id]: request_cache_dict[user_id][relationship] = milestones_api.get_course_content_milestones( course_key=course_id, relationship=relationship, user={"id": user_id} ) if content_id is None: return request_cache_dict[user_id][relationship] return [m for m in request_cache_dict[user_id][relationship] if m['content_id'] == six.text_type(content_id)]
def create_new_event_transaction_id(): """ Sets the event transaction id to a newly- generated UUID. """ new_id = uuid4() get_cache('event_transaction')['id'] = new_id return new_id
def set_event_transaction_id(new_id): """ Sets the event transaction id to a UUID object generated from new_id. new_id must be a parsable string version of a UUID. """ get_cache('event_transaction')['id'] = UUID(new_id)
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) }
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) }
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'): 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()
def prefetch(cls, users): # lint-amnesty, pylint: disable=missing-function-docstring 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'): roles_by_user[role.user.id].add(role) users_without_roles = [u for u in users if u.id not in roles_by_user] for user in users_without_roles: roles_by_user[user.id] = set()
def get_override(cls, user_id, usage_key): prefetch_values = get_cache(cls._CACHE_NAMESPACE).get((user_id, str(usage_key.course_key)), None) if prefetch_values is not None: return prefetch_values.get(usage_key) try: return cls.objects.get( grade__user_id=user_id, grade__course_id=usage_key.course_key, grade__usage_key=usage_key, ) except PersistentSubsectionGradeOverride.DoesNotExist: pass
def _initialize_cache(cls, user_id, course_key): """ Prefetches visible blocks for the given user and course and stores in the cache. Returns a dictionary mapping hashes of these block records to the block record objects. """ grades_with_blocks = PersistentSubsectionGrade.objects.select_related('visible_blocks').filter( user_id=user_id, course_id=course_key, ) prefetched = {grade.visible_blocks.hashed: grade.visible_blocks for grade in grades_with_blocks} get_cache(cls._CACHE_NAMESPACE)[cls._cache_key(user_id, course_key)] = prefetched return prefetched
def bulk_read(cls, user_id, course_key): """ Reads and returns all visible block records for the given user and course from the cache. The cache is initialized with the visible blocks for this user and course if no entry currently exists. Arguments: course_key: The course identifier for the desired records """ prefetched = get_cache(cls._CACHE_NAMESPACE).get(cls._cache_key(user_id, course_key), None) if prefetched is None: prefetched = cls._initialize_cache(user_id, course_key) return prefetched
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'): course_tags[tag.user.id][tag.key] = tag.value get_cache(cls.CACHE_NAMESPACE)[cls._cache_key(course_id)] = course_tags
def get_current_ccx(course_key): """ Return the ccx that is active for this course. course_key is expected to be an instance of an opaque CourseKey, a ValueError is raised if this expectation is not met. """ if not isinstance(course_key, CourseKey): raise ValueError("get_current_ccx requires a CourseKey instance") if not isinstance(course_key, CCXLocator): return None ccx_cache = get_cache('ccx') if course_key not in ccx_cache: ccx_cache[course_key] = CustomCourseForEdX.objects.get(pk=course_key.ccx) return ccx_cache[course_key]
def _get_version_info(self, request): """ Gets and Sets version related info in mem cache and request cache; and returns a dict of it. It sets request cache data for last_supported_date and latest_version with memcached values if exists against user app properties else computes the values for specific platform and sets it in both memcache (for next server interaction from same app version/platform) and request cache Returns: dict: Containing app version info """ user_agent = request.META.get('HTTP_USER_AGENT') if user_agent: platform = self._get_platform(request, user_agent) if platform: request_cache_dict = get_cache(self.REQUEST_CACHE_NAME) request_cache_dict[self.USER_APP_VERSION] = platform.version last_supported_date_cache_key = self._get_cache_key_name( self.LAST_SUPPORTED_DATE_HEADER, platform.version) latest_version_cache_key = self._get_cache_key_name( self.LATEST_VERSION_HEADER, platform.NAME) cached_data = cache.get_many( [last_supported_date_cache_key, latest_version_cache_key]) last_supported_date = cached_data.get( last_supported_date_cache_key) if last_supported_date != self.NO_LAST_SUPPORTED_DATE and not isinstance( last_supported_date, datetime): last_supported_date = self._get_last_supported_date( platform.NAME, platform.version) cache.set(last_supported_date_cache_key, last_supported_date, self.CACHE_TIMEOUT) request_cache_dict[ self.LAST_SUPPORTED_DATE_HEADER] = last_supported_date latest_version = cached_data.get(latest_version_cache_key) if not (latest_version and isinstance(latest_version, six.text_type)): latest_version = self._get_latest_version(platform.NAME) cache.set(latest_version_cache_key, latest_version, self.CACHE_TIMEOUT) request_cache_dict[self.LATEST_VERSION_HEADER] = latest_version return request_cache_dict
def get_current_ccx(course_key): """ Return the ccx that is active for this course. course_key is expected to be an instance of an opaque CourseKey, a ValueError is raised if this expectation is not met. """ if not isinstance(course_key, CourseKey): raise ValueError("get_current_ccx requires a CourseKey instance") if not isinstance(course_key, CCXLocator): return None ccx_cache = get_cache('ccx') if course_key not in ccx_cache: ccx_cache[course_key] = CustomCourseForEdX.objects.get( pk=course_key.ccx) return ccx_cache[course_key]
def enable_named_outer_atomic(*names): """ Enable outer_atomics with names. Can be used either as a decorator or a context manager. See docstring of outer_atomic for details. Arguments: names (variable-lenght argument list): Names of outer_atomics. """ if len(names) == 0: raise ValueError("At least one name must be specified.") cache = get_cache(OUTER_ATOMIC_CACHE_NAME) for name in names: cache[name] = True yield for name in names: del cache[name]
def _get_overrides_for_ccx(ccx): """ Returns a dictionary mapping field name to overriden value for any overrides set on this block for this CCX. """ overrides_cache = get_cache('ccx-overrides') if ccx not in overrides_cache: overrides = {} query = CcxFieldOverride.objects.filter(ccx=ccx, ) for override in query: block_overrides = overrides.setdefault(override.location, {}) block_overrides[override.field] = json.loads(override.value) block_overrides[override.field + "_id"] = override.id block_overrides[override.field + "_instance"] = override overrides_cache[ccx] = overrides return overrides_cache[ccx]
def process_response(self, __, response): """ If request is from mobile native app, then add version related info to response headers. Returns: Http response: with additional headers; 1. EDX-APP-LATEST-VERSION; if user app version < latest available version 2. EDX-APP-VERSION-LAST-SUPPORTED-DATE; if user app version < min supported version and timestamp < expiry of that version """ request_cache_dict = get_cache(self.REQUEST_CACHE_NAME) if request_cache_dict: last_supported_date = request_cache_dict[self.LAST_SUPPORTED_DATE_HEADER] if last_supported_date != self.NO_LAST_SUPPORTED_DATE: response[self.LAST_SUPPORTED_DATE_HEADER] = last_supported_date.isoformat() latest_version = request_cache_dict[self.LATEST_VERSION_HEADER] user_app_version = request_cache_dict[self.USER_APP_VERSION] if (latest_version != self.NO_LATEST_VERSION and parsed_version(user_app_version) < parsed_version(latest_version)): response[self.LATEST_VERSION_HEADER] = latest_version return response
def read(cls, user_id, course_id): """ Reads a grade from database Arguments: user_id: The user associated with the desired grade course_id: The id of the course associated with the desired grade 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 dict, 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)
def bulk_read_grades(cls, user_id, course_key): """ Reads all grades for the given user and course. Arguments: user_id: The user associated with the desired grades course_key: The course identifier for the desired grades """ try: prefetched_grades = get_cache(cls._CACHE_NAMESPACE)[cls._cache_key(course_key)] try: return prefetched_grades[user_id] except KeyError: # The user's grade is not in the cached dict of subsection grades, # so return an empty list. return [] except KeyError: # subsection grades were not prefetched for the course, so get them from the DB return cls.objects.select_related('visible_blocks', 'override').filter( user_id=user_id, course_id=course_key, )
def _get_overrides_for_ccx(ccx): """ Returns a dictionary mapping field name to overriden value for any overrides set on this block for this CCX. """ overrides_cache = get_cache('ccx-overrides') if ccx not in overrides_cache: overrides = {} query = CcxFieldOverride.objects.filter( ccx=ccx, ) for override in query: block_overrides = overrides.setdefault(override.location, {}) block_overrides[override.field] = json.loads(override.value) block_overrides[override.field + "_id"] = override.id block_overrides[override.field + "_instance"] = override overrides_cache[ccx] = overrides return overrides_cache[ccx]
def _get_version_info(self, request): """ Gets and Sets version related info in mem cache and request cache; and returns a dict of it. It sets request cache data for last_supported_date and latest_version with memcached values if exists against user app properties else computes the values for specific platform and sets it in both memcache (for next server interaction from same app version/platform) and request cache Returns: dict: Containing app version info """ user_agent = request.META.get('HTTP_USER_AGENT') if user_agent: platform = self._get_platform(request, user_agent) if platform: request_cache_dict = get_cache(self.REQUEST_CACHE_NAME) request_cache_dict[self.USER_APP_VERSION] = platform.version last_supported_date_cache_key = self._get_cache_key_name( self.LAST_SUPPORTED_DATE_HEADER, platform.version ) latest_version_cache_key = self._get_cache_key_name(self.LATEST_VERSION_HEADER, platform.NAME) cached_data = cache.get_many([last_supported_date_cache_key, latest_version_cache_key]) last_supported_date = cached_data.get(last_supported_date_cache_key) if not last_supported_date: last_supported_date = self._get_last_supported_date(platform.NAME, platform.version) cache.set(last_supported_date_cache_key, last_supported_date, self.CACHE_TIMEOUT) request_cache_dict[self.LAST_SUPPORTED_DATE_HEADER] = last_supported_date latest_version = cached_data.get(latest_version_cache_key) if not latest_version: latest_version = self._get_latest_version(platform.NAME) cache.set(latest_version_cache_key, latest_version, self.CACHE_TIMEOUT) request_cache_dict[self.LATEST_VERSION_HEADER] = latest_version return request_cache_dict
def get_course_content_milestones(course_id, content_id=None, relationship='requires', user_id=None): """ Client API operation adapter/wrapper Uses the request cache to store all of a user's milestones Returns all content blocks in a course if content_id is None, otherwise it just returns that specific content block. """ if not ENABLE_MILESTONES_APP.is_enabled(): return [] if user_id is None: return milestones_api.get_course_content_milestones( course_id, content_id, relationship) request_cache_dict = get_cache(REQUEST_CACHE_NAME) if user_id not in request_cache_dict: request_cache_dict[user_id] = {} if relationship not in request_cache_dict[user_id]: request_cache_dict[user_id][ relationship] = milestones_api.get_course_content_milestones( course_key=course_id, relationship=relationship, user={"id": user_id}) if content_id is None: return request_cache_dict[user_id][relationship] return [ m for m in request_cache_dict[user_id][relationship] if m['content_id'] == str(content_id) ]
def __enter__(self): connection = transaction.get_connection(self.using) cache = get_cache(OUTER_ATOMIC_CACHE_NAME) # By default it is enabled. enable = True # If name is set it is only enabled if requested by calling enable_named_outer_atomic(). if self.name: enable = cache.get(self.name, False) if enable: # TestCase setup nests tests in two atomics - one for the test class and one for the individual test. # The outermost atomic starts a transaction - so does not have a savepoint. # The inner atomic starts a savepoint around the test. # So, for tests only, there should be exactly one savepoint_id and two atomic_for_testcase_calls. # atomic_for_testcase_calls below is added in a monkey-patch for tests only. if self.ALLOW_NESTED and (self.atomic_for_testcase_calls - len(connection.savepoint_ids)) < 1: raise transaction.TransactionManagementError( 'Cannot be inside an atomic block.') # Otherwise, this shouldn't be nested in any atomic block. if not self.ALLOW_NESTED and connection.in_atomic_block: raise transaction.TransactionManagementError( 'Cannot be inside an atomic block.') # This will set the transaction isolation level to READ COMMITTED for the next transaction. if self.read_committed is True: if connection.vendor == 'mysql': cursor = connection.cursor() cursor.execute( "SET TRANSACTION ISOLATION LEVEL READ COMMITTED") super(OuterAtomic, self).__enter__()
def cached_get_or_create(cls, user_id, blocks): """ Given a ``user_id`` and a ``BlockRecordList`` object, attempts to fetch the related VisibleBlocks model from the request cache. This will create and save a new ``VisibleBlocks`` record if no record exists corresponding to the hash_value of ``blocks``. """ prefetched = get_cache(cls._CACHE_NAMESPACE).get(cls._cache_key(user_id, blocks.course_key)) if prefetched is not None: model = prefetched.get(blocks.hash_value) if not model: # We still have to do a get_or_create, because # another user may have had this block hash created, # even if the user we checked the cache for hasn't yet. model, _ = cls.objects.get_or_create( hashed=blocks.hash_value, blocks_json=blocks.json_value, course_id=blocks.course_key, ) cls._update_cache(user_id, blocks.course_key, [model]) else: model, _ = cls.objects.get_or_create( hashed=blocks.hash_value, defaults={u'blocks_json': blocks.json_value, u'course_id': blocks.course_key}, ) return model
def clear_prefetched_data(cls, course_key): """ Clears prefetched grades for this course from the RequestCache. """ get_cache(cls._CACHE_NAMESPACE).pop(cls._cache_key(course_key), None)
def _update_cache(cls, course_id, user_id, grade): course_cache = get_cache(cls._CACHE_NAMESPACE).get( cls._cache_key(course_id)) if course_cache is not None: course_cache[user_id] = grade
def prefetch(cls, user_id, course_key): get_cache(cls._CACHE_NAMESPACE)[(user_id, str(course_key))] = { override.grade.usage_key: override for override in cls.objects.filter(grade__user_id=user_id, grade__course_id=course_key) }
def is_prefetched(cls, course_id): return cls._cache_key(course_id) in get_cache(cls.CACHE_NAMESPACE)
def _update_cache(cls, course_id, user_id, grade): course_cache = get_cache(cls._CACHE_NAMESPACE).get(cls._cache_key(course_id)) if course_cache is not None: course_cache[user_id] = grade
def get_user_roles(cls, user): return get_cache(cls.CACHE_NAMESPACE)[cls.CACHE_KEY][user.id]
def get_course_tag(cls, user_id, course_id, key): return get_cache(cls.CACHE_NAMESPACE)[cls._cache_key(course_id)][user_id][key]
def set_event_transaction_type(action_type): """ Takes a string and stores it in the request cache as the user action type. """ get_cache('event_transaction')['type'] = action_type
def get_event_transaction_id(): """ Retrieves the current event transaction id from the request cache. """ return get_cache('event_transaction').get('id', None)
def get_event_transaction_type(): """ Retrieves the current event transaction type from the request cache. """ return get_cache('event_transaction').get('type', None)