Commit cf834c05 by Vedran Karacic

Add caching of verification statuses; rename hashing variables.

The current naming can be confusing and cause issues in the future.
parent f7b8f0cb
import datetime import datetime
import hashlib
import logging import logging
from urlparse import urljoin from urlparse import urljoin
from analytics import Client as SegmentClient from analytics import Client as SegmentClient
from dateutil.parser import parse
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
...@@ -10,6 +12,7 @@ from django.core.cache import cache ...@@ -10,6 +12,7 @@ from django.core.cache import cache
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.timezone import now
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from edx_rest_api_client.client import EdxRestApiClient from edx_rest_api_client.client import EdxRestApiClient
from jsonfield.fields import JSONField from jsonfield.fields import JSONField
...@@ -453,6 +456,7 @@ class User(AbstractUser): ...@@ -453,6 +456,7 @@ class User(AbstractUser):
""" """
Check if a user has verified his/her identity. Check if a user has verified his/her identity.
Calls the LMS verification status API endpoint and returns the verification status information. Calls the LMS verification status API endpoint and returns the verification status information.
The status information is stored in cache, if the user is verified, until the verification expires.
Args: Args:
site (Site): The site object from which the LMS account API endpoint is created. site (Site): The site object from which the LMS account API endpoint is created.
...@@ -465,12 +469,21 @@ class User(AbstractUser): ...@@ -465,12 +469,21 @@ class User(AbstractUser):
establishing a connection with the LMS verification status API endpoint. establishing a connection with the LMS verification status API endpoint.
""" """
try: try:
api = EdxRestApiClient( cache_key = 'verification_status_{username}'.format(username=self.username)
site.siteconfiguration.build_lms_url('api/user/v1/'), cache_key = hashlib.md5(cache_key).hexdigest()
oauth_access_token=self.access_token verification = cache.get(cache_key)
) if not verification:
response = api.accounts(self.username).verification_status().get() api = EdxRestApiClient(
return response.get('is_verified', False) site.siteconfiguration.build_lms_url('api/user/v1/'),
oauth_access_token=self.access_token
)
response = api.accounts(self.username).verification_status().get()
verification = response.get('is_verified', False)
if verification:
cache_timeout = int((parse(response.get('expiration_datetime')) - now()).total_seconds())
cache.set(cache_key, verification, cache_timeout)
return verification
except HttpNotFoundError: except HttpNotFoundError:
return False return False
except (ConnectionError, SlumberBaseException, Timeout): except (ConnectionError, SlumberBaseException, Timeout):
......
...@@ -142,6 +142,27 @@ class UserTests(CourseCatalogTestMixin, LmsApiMockMixin, TestCase): ...@@ -142,6 +142,27 @@ class UserTests(CourseCatalogTestMixin, LmsApiMockMixin, TestCase):
with self.assertRaises(VerificationStatusError): with self.assertRaises(VerificationStatusError):
user.is_verified(self.site) user.is_verified(self.site)
@httpretty.activate
def test_user_verification_status_cache(self):
""" Verify the user verification status values are cached. """
user = self.create_user()
self.mock_verification_status_api(self.site, user)
self.assertTrue(user.is_verified(self.site))
httpretty.disable()
self.assertTrue(user.is_verified(self.site))
@httpretty.activate
def test_user_verification_status_not_cached(self):
""" Verify the user verification status values is not cached when user is not verified. """
user = self.create_user()
self.mock_verification_status_api(self.site, user, is_verified=False)
self.assertFalse(user.is_verified(self.site))
httpretty.disable()
with self.assertRaises(VerificationStatusError):
user.is_verified(self.site)
class BusinessClientTests(TestCase): class BusinessClientTests(TestCase):
def test_str(self): def test_str(self):
......
...@@ -23,8 +23,8 @@ def get_range_catalog_query_results(limit, query, site, offset=None): ...@@ -23,8 +23,8 @@ def get_range_catalog_query_results(limit, query, site, offset=None):
""" """
partner_code = site.siteconfiguration.partner.short_code partner_code = site.siteconfiguration.partner.short_code
cache_key = 'course_runs_{}_{}_{}_{}'.format(query, limit, offset, partner_code) cache_key = 'course_runs_{}_{}_{}_{}'.format(query, limit, offset, partner_code)
cache_hash = hashlib.md5(cache_key).hexdigest() cache_key = hashlib.md5(cache_key).hexdigest()
response = cache.get(cache_hash) response = cache.get(cache_key)
if not response: if not response:
response = site.siteconfiguration.course_catalog_api_client.course_runs.get( response = site.siteconfiguration.course_catalog_api_client.course_runs.get(
limit=limit, limit=limit,
...@@ -32,7 +32,7 @@ def get_range_catalog_query_results(limit, query, site, offset=None): ...@@ -32,7 +32,7 @@ def get_range_catalog_query_results(limit, query, site, offset=None):
q=query, q=query,
partner=partner_code partner=partner_code
) )
cache.set(cache_hash, response, settings.COURSES_API_CACHE_TIMEOUT) cache.set(cache_key, response, settings.COURSES_API_CACHE_TIMEOUT)
return response return response
......
...@@ -49,15 +49,15 @@ class UtilsTests(CourseCatalogTestMixin, CourseCatalogMockMixin, TestCase): ...@@ -49,15 +49,15 @@ class UtilsTests(CourseCatalogTestMixin, CourseCatalogMockMixin, TestCase):
self.mock_dynamic_catalog_single_course_runs_api(course) self.mock_dynamic_catalog_single_course_runs_api(course)
cache_key = 'courses_api_detail_{}{}'.format(course.id, self.site.siteconfiguration.partner.short_code) cache_key = 'courses_api_detail_{}{}'.format(course.id, self.site.siteconfiguration.partner.short_code)
cache_hash = hashlib.md5(cache_key).hexdigest() cache_key = hashlib.md5(cache_key).hexdigest()
cached_course = cache.get(cache_hash) cached_course = cache.get(cache_key)
self.assertIsNone(cached_course) self.assertIsNone(cached_course)
response = get_course_info_from_catalog(self.request.site, course) response = get_course_info_from_catalog(self.request.site, course)
self.assertEqual(response['title'], course.name) self.assertEqual(response['title'], course.name)
cached_course = cache.get(cache_hash) cached_course = cache.get(cache_key)
self.assertEqual(cached_course, response) self.assertEqual(cached_course, response)
@ddt.data( @ddt.data(
......
...@@ -25,11 +25,11 @@ def get_course_info_from_catalog(site, course_key): ...@@ -25,11 +25,11 @@ def get_course_info_from_catalog(site, course_key):
api = site.siteconfiguration.course_catalog_api_client api = site.siteconfiguration.course_catalog_api_client
partner_short_code = site.siteconfiguration.partner.short_code partner_short_code = site.siteconfiguration.partner.short_code
cache_key = 'courses_api_detail_{}{}'.format(course_key, partner_short_code) cache_key = 'courses_api_detail_{}{}'.format(course_key, partner_short_code)
cache_hash = hashlib.md5(cache_key).hexdigest() cache_key = hashlib.md5(cache_key).hexdigest()
course_run = cache.get(cache_hash) course_run = cache.get(cache_key)
if not course_run: # pragma: no cover if not course_run: # pragma: no cover
course_run = api.course_runs(course_key).get(partner=partner_short_code) course_run = api.course_runs(course_key).get(partner=partner_short_code)
cache.set(cache_hash, course_run, settings.COURSES_API_CACHE_TIMEOUT) cache.set(cache_key, course_run, settings.COURSES_API_CACHE_TIMEOUT)
return course_run return course_run
......
...@@ -405,13 +405,13 @@ class BasketSummaryViewTests(CourseCatalogTestMixin, CourseCatalogMockMixin, Lms ...@@ -405,13 +405,13 @@ class BasketSummaryViewTests(CourseCatalogTestMixin, CourseCatalogMockMixin, Lms
self.mock_dynamic_catalog_single_course_runs_api(self.course) self.mock_dynamic_catalog_single_course_runs_api(self.course)
cache_key = 'courses_api_detail_{}{}'.format(self.course.id, self.site.siteconfiguration.partner.short_code) cache_key = 'courses_api_detail_{}{}'.format(self.course.id, self.site.siteconfiguration.partner.short_code)
cache_hash = hashlib.md5(cache_key).hexdigest() cache_key = hashlib.md5(cache_key).hexdigest()
cached_course_before = cache.get(cache_hash) cached_course_before = cache.get(cache_key)
self.assertIsNone(cached_course_before) self.assertIsNone(cached_course_before)
response = self.client.get(self.path) response = self.client.get(self.path)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
cached_course_after = cache.get(cache_hash) cached_course_after = cache.get(cache_key)
self.assertEqual(cached_course_after['title'], self.course.name) self.assertEqual(cached_course_after['title'], self.course.name)
@ddt.data({ @ddt.data({
......
...@@ -116,8 +116,8 @@ class Range(AbstractRange): ...@@ -116,8 +116,8 @@ class Range(AbstractRange):
Retrieve the results from running the query contained in catalog_query field. Retrieve the results from running the query contained in catalog_query field.
""" """
cache_key = 'catalog_query_contains [{}] [{}]'.format(self.catalog_query, product.course_id) cache_key = 'catalog_query_contains [{}] [{}]'.format(self.catalog_query, product.course_id)
cache_hash = hashlib.md5(cache_key).hexdigest() cache_key = hashlib.md5(cache_key).hexdigest()
response = cache.get(cache_hash) response = cache.get(cache_key)
if not response: # pragma: no cover if not response: # pragma: no cover
request = get_current_request() request = get_current_request()
try: try:
...@@ -126,7 +126,7 @@ class Range(AbstractRange): ...@@ -126,7 +126,7 @@ class Range(AbstractRange):
course_run_ids=product.course_id, course_run_ids=product.course_id,
partner=request.site.siteconfiguration.partner.short_code partner=request.site.siteconfiguration.partner.short_code
) )
cache.set(cache_hash, response, settings.COURSES_API_CACHE_TIMEOUT) cache.set(cache_key, response, settings.COURSES_API_CACHE_TIMEOUT)
except: # pylint: disable=bare-except except: # pylint: disable=bare-except
raise Exception('Could not contact Course Catalog Service.') raise Exception('Could not contact Course Catalog Service.')
......
...@@ -96,14 +96,14 @@ class RangeTests(CouponMixin, CourseCatalogTestMixin, CourseCatalogMockMixin, Te ...@@ -96,14 +96,14 @@ class RangeTests(CouponMixin, CourseCatalogTestMixin, CourseCatalogMockMixin, Te
self.range.catalog_query = 'key:*' self.range.catalog_query = 'key:*'
cache_key = 'catalog_query_contains [{}] [{}]'.format('key:*', seat.course_id) cache_key = 'catalog_query_contains [{}] [{}]'.format('key:*', seat.course_id)
cache_hash = hashlib.md5(cache_key).hexdigest() cache_key = hashlib.md5(cache_key).hexdigest()
cached_response = cache.get(cache_hash) cached_response = cache.get(cache_key)
self.assertIsNone(cached_response) self.assertIsNone(cached_response)
with mock.patch('ecommerce.core.url_utils.get_current_request', mock.Mock(return_value=request)): with mock.patch('ecommerce.core.url_utils.get_current_request', mock.Mock(return_value=request)):
response = self.range.run_catalog_query(seat) response = self.range.run_catalog_query(seat)
self.assertTrue(response['course_runs'][course.id]) self.assertTrue(response['course_runs'][course.id])
cached_response = cache.get(cache_hash) cached_response = cache.get(cache_key)
self.assertEqual(response, cached_response) self.assertEqual(response, cached_response)
@httpretty.activate @httpretty.activate
......
...@@ -553,11 +553,11 @@ def get_cached_voucher(code): ...@@ -553,11 +553,11 @@ def get_cached_voucher(code):
Voucher.DoesNotExist: When no vouchers with provided code exist. Voucher.DoesNotExist: When no vouchers with provided code exist.
""" """
cache_key = 'voucher_{code}'.format(code=code) cache_key = 'voucher_{code}'.format(code=code)
cache_hash = hashlib.md5(cache_key).hexdigest() cache_key = hashlib.md5(cache_key).hexdigest()
voucher = cache.get(cache_key) voucher = cache.get(cache_key)
if not voucher: if not voucher:
voucher = Voucher.objects.get(code=code) voucher = Voucher.objects.get(code=code)
cache.set(cache_hash, voucher, settings.VOUCHER_CACHE_TIMEOUT) cache.set(cache_key, voucher, settings.VOUCHER_CACHE_TIMEOUT)
return voucher return voucher
......
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