Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
ecommerce
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
ecommerce
Commits
066a7648
Commit
066a7648
authored
Nov 17, 2016
by
Vedran Karačić
Committed by
GitHub
Nov 17, 2016
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1016 from edx/vkaracic/is_verified
Add custom verification status exception.
parents
b24d3422
cf834c05
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
83 additions
and
38 deletions
+83
-38
ecommerce/core/exceptions.py
+5
-0
ecommerce/core/models.py
+26
-14
ecommerce/core/tests/test_models.py
+30
-2
ecommerce/coupons/utils.py
+3
-3
ecommerce/courses/tests/test_utils.py
+3
-3
ecommerce/courses/utils.py
+3
-3
ecommerce/extensions/basket/tests/test_views.py
+3
-3
ecommerce/extensions/offer/models.py
+3
-3
ecommerce/extensions/offer/tests/test_models.py
+3
-3
ecommerce/extensions/voucher/utils.py
+2
-2
ecommerce/tests/mixins.py
+2
-2
No files found.
ecommerce/core/exceptions.py
View file @
066a7648
...
@@ -6,3 +6,8 @@ class MissingRequestError(Exception):
...
@@ -6,3 +6,8 @@ class MissingRequestError(Exception):
class
SiteConfigurationError
(
Exception
):
class
SiteConfigurationError
(
Exception
):
""" Raised when SiteConfiguration is invalid. """
""" Raised when SiteConfiguration is invalid. """
pass
pass
class
VerificationStatusError
(
Exception
):
""" Raised when the verification fails to connect to LMS. """
pass
ecommerce/core/models.py
View file @
066a7648
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,12 +12,14 @@ from django.core.cache import cache
...
@@ -10,12 +12,14 @@ 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
from
requests.exceptions
import
ConnectionError
,
Timeout
from
requests.exceptions
import
ConnectionError
,
Timeout
from
slumber.exceptions
import
HttpNotFoundError
,
SlumberBaseException
from
slumber.exceptions
import
HttpNotFoundError
,
SlumberBaseException
from
ecommerce.core.exceptions
import
VerificationStatusError
from
ecommerce.core.url_utils
import
get_lms_url
from
ecommerce.core.url_utils
import
get_lms_url
from
ecommerce.courses.utils
import
mode_for_seat
from
ecommerce.courses.utils
import
mode_for_seat
from
ecommerce.extensions.payment.exceptions
import
ProcessorNotFoundError
from
ecommerce.extensions.payment.exceptions
import
ProcessorNotFoundError
...
@@ -448,13 +452,14 @@ class User(AbstractUser):
...
@@ -448,13 +452,14 @@ class User(AbstractUser):
raise
raise
return
response
return
response
def
is_verified
(
self
,
request
):
def
is_verified
(
self
,
site
):
"""
"""
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:
request (WSGIRequest): The reques
t from which the LMS account API endpoint is created.
site (Site): The site objec
t from which the LMS account API endpoint is created.
Returns:
Returns:
True if the user is verified, false otherwise.
True if the user is verified, false otherwise.
...
@@ -464,20 +469,27 @@ class User(AbstractUser):
...
@@ -464,20 +469,27 @@ 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
)
request
.
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
):
# pragma: no cover
except
(
ConnectionError
,
SlumberBaseException
,
Timeout
):
log
.
exception
(
msg
=
'Failed to retrieve verification status details for [{username}]'
.
format
(
username
=
self
.
username
)
'Failed to retrieve verification status details for [
%
s]'
,
log
.
exception
(
msg
)
self
.
username
raise
VerificationStatusError
(
msg
)
)
raise
class
Client
(
User
):
class
Client
(
User
):
...
...
ecommerce/core/tests/test_models.py
View file @
066a7648
...
@@ -8,6 +8,7 @@ from django.test import override_settings
...
@@ -8,6 +8,7 @@ from django.test import override_settings
from
edx_rest_api_client.auth
import
SuppliedJwtAuth
from
edx_rest_api_client.auth
import
SuppliedJwtAuth
from
requests.exceptions
import
ConnectionError
from
requests.exceptions
import
ConnectionError
from
ecommerce.core.exceptions
import
VerificationStatusError
from
ecommerce.core.models
import
BusinessClient
,
User
,
SiteConfiguration
,
validate_configuration
from
ecommerce.core.models
import
BusinessClient
,
User
,
SiteConfiguration
,
validate_configuration
from
ecommerce.core.tests
import
toggle_switch
from
ecommerce.core.tests
import
toggle_switch
from
ecommerce.extensions.catalogue.tests.mixins
import
CourseCatalogTestMixin
from
ecommerce.extensions.catalogue.tests.mixins
import
CourseCatalogTestMixin
...
@@ -132,8 +133,35 @@ class UserTests(CourseCatalogTestMixin, LmsApiMockMixin, TestCase):
...
@@ -132,8 +133,35 @@ class UserTests(CourseCatalogTestMixin, LmsApiMockMixin, TestCase):
def
test_user_verification_status
(
self
,
status_code
,
is_verified
):
def
test_user_verification_status
(
self
,
status_code
,
is_verified
):
""" Verify the method returns correct response. """
""" Verify the method returns correct response. """
user
=
self
.
create_user
()
user
=
self
.
create_user
()
self
.
mock_verification_status_api
(
self
.
request
,
user
,
status
=
status_code
,
is_verified
=
is_verified
)
self
.
mock_verification_status_api
(
self
.
site
,
user
,
status
=
status_code
,
is_verified
=
is_verified
)
self
.
assertEqual
(
user
.
is_verified
(
self
.
request
),
is_verified
)
self
.
assertEqual
(
user
.
is_verified
(
self
.
site
),
is_verified
)
def
test_user_verification_connection_error
(
self
):
""" Verify verification status exception is raised for connection issues. """
user
=
self
.
create_user
()
with
self
.
assertRaises
(
VerificationStatusError
):
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
):
...
...
ecommerce/coupons/utils.py
View file @
066a7648
...
@@ -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
...
...
ecommerce/courses/tests/test_utils.py
View file @
066a7648
...
@@ -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
(
...
...
ecommerce/courses/utils.py
View file @
066a7648
...
@@ -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
...
...
ecommerce/extensions/basket/tests/test_views.py
View file @
066a7648
...
@@ -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
({
...
...
ecommerce/extensions/offer/models.py
View file @
066a7648
...
@@ -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.'
)
...
...
ecommerce/extensions/offer/tests/test_models.py
View file @
066a7648
...
@@ -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
...
...
ecommerce/extensions/voucher/utils.py
View file @
066a7648
...
@@ -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
...
...
ecommerce/tests/mixins.py
View file @
066a7648
...
@@ -382,7 +382,7 @@ class LmsApiMockMixin(object):
...
@@ -382,7 +382,7 @@ class LmsApiMockMixin(object):
)
)
httpretty
.
register_uri
(
httpretty
.
GET
,
url
,
body
=
json
.
dumps
(
eligibility_data
),
content_type
=
CONTENT_TYPE
)
httpretty
.
register_uri
(
httpretty
.
GET
,
url
,
body
=
json
.
dumps
(
eligibility_data
),
content_type
=
CONTENT_TYPE
)
def
mock_verification_status_api
(
self
,
request
,
user
,
status
=
200
,
is_verified
=
True
):
def
mock_verification_status_api
(
self
,
site
,
user
,
status
=
200
,
is_verified
=
True
):
""" Mock verification API endpoint. Returns verfication status data. """
""" Mock verification API endpoint. Returns verfication status data. """
verification_data
=
{
verification_data
=
{
'status'
:
'approved'
,
'status'
:
'approved'
,
...
@@ -390,7 +390,7 @@ class LmsApiMockMixin(object):
...
@@ -390,7 +390,7 @@ class LmsApiMockMixin(object):
'is_verified'
:
is_verified
'is_verified'
:
is_verified
}
}
url
=
'{host}/accounts/{username}/verification_status/'
.
format
(
url
=
'{host}/accounts/{username}/verification_status/'
.
format
(
host
=
request
.
site
.
siteconfiguration
.
build_lms_url
(
'/api/user/v1'
),
host
=
site
.
siteconfiguration
.
build_lms_url
(
'/api/user/v1'
),
username
=
user
.
username
username
=
user
.
username
)
)
httpretty
.
register_uri
(
httpretty
.
register_uri
(
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment