Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
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
edx-platform
Commits
3a65ca02
Commit
3a65ca02
authored
Jul 28, 2016
by
Kevin Kim
Committed by
GitHub
Jul 28, 2016
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #13023 from edx/kkim/country_tz_api
Country Time Zone API
parents
a35a6b73
127095b9
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
194 additions
and
38 deletions
+194
-38
openedx/core/djangoapps/user_api/errors.py
+5
-0
openedx/core/djangoapps/user_api/legacy_urls.py
+4
-0
openedx/core/djangoapps/user_api/preferences/api.py
+21
-3
openedx/core/djangoapps/user_api/preferences/tests/test_api.py
+36
-5
openedx/core/djangoapps/user_api/serializers.py
+22
-0
openedx/core/djangoapps/user_api/tests/test_views.py
+39
-1
openedx/core/djangoapps/user_api/views.py
+36
-2
openedx/core/lib/tests/test_time_zone_utils.py
+26
-23
openedx/core/lib/time_zone_utils.py
+5
-4
No files found.
openedx/core/djangoapps/user_api/errors.py
View file @
3a65ca02
...
...
@@ -93,3 +93,8 @@ class PreferenceUpdateError(PreferenceRequestError):
def
__init__
(
self
,
developer_message
,
user_message
=
None
):
self
.
developer_message
=
developer_message
self
.
user_message
=
user_message
class
CountryCodeError
(
ValueError
):
"""There was a problem with the country code"""
pass
openedx/core/djangoapps/user_api/legacy_urls.py
View file @
3a65ca02
...
...
@@ -29,6 +29,10 @@ urlpatterns = patterns(
user_api_views
.
UpdateEmailOptInPreference
.
as_view
(),
name
=
"preferences_email_opt_in"
),
url
(
r'^v1/preferences/time_zones/$'
,
user_api_views
.
CountryTimeZoneListView
.
as_view
(),
),
)
if
settings
.
FEATURES
.
get
(
'ENABLE_COMBINED_LOGIN_REGISTRATION'
):
...
...
openedx/core/djangoapps/user_api/preferences/api.py
View file @
3a65ca02
...
...
@@ -7,22 +7,22 @@ from eventtracking import tracker
from
django.conf
import
settings
from
django.core.exceptions
import
ObjectDoesNotExist
from
django_countries
import
countries
from
django.db
import
IntegrityError
from
django.utils.translation
import
ugettext
as
_
from
django.utils.translation
import
ugettext_noop
from
pytz
import
common_timezones
,
common_timezones_set
,
country_timezones
from
student.models
import
User
,
UserProfile
from
request_cache
import
get_request_or_stub
from
..errors
import
(
UserAPIInternalError
,
UserAPIRequestError
,
UserNotFound
,
UserNotAuthorized
,
PreferenceValidationError
,
PreferenceUpdateError
PreferenceValidationError
,
PreferenceUpdateError
,
CountryCodeError
)
from
..helpers
import
intercept_errors
from
..models
import
UserOrgTag
,
UserPreference
from
..serializers
import
UserSerializer
,
RawUserPreferenceSerializer
from
pytz
import
common_timezones_set
log
=
logging
.
getLogger
(
__name__
)
...
...
@@ -417,3 +417,21 @@ def _create_preference_update_error(preference_key, preference_value, error):
key
=
preference_key
,
value
=
preference_value
),
)
def
get_country_time_zones
(
country_code
=
None
):
"""
Returns a list of time zones commonly used in given country
or list of all time zones, if country code is None.
Arguments:
country_code (str): ISO 3166-1 Alpha-2 country code
Raises:
CountryCodeError: the given country code is invalid
"""
if
country_code
is
None
:
return
common_timezones
if
country_code
.
upper
()
in
set
(
countries
.
alt_codes
):
return
country_timezones
(
country_code
)
raise
CountryCodeError
openedx/core/djangoapps/user_api/preferences/tests/test_api.py
View file @
3a65ca02
...
...
@@ -7,7 +7,7 @@ import ddt
import
unittest
from
mock
import
patch
from
nose.plugins.attrib
import
attr
from
pytz
import
UTC
from
pytz
import
common_timezones
,
utc
from
django.conf
import
settings
from
django.contrib.auth.models
import
User
...
...
@@ -21,11 +21,22 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
...accounts.api
import
create_account
from
...errors
import
UserNotFound
,
UserNotAuthorized
,
PreferenceValidationError
,
PreferenceUpdateError
from
...errors
import
(
UserNotFound
,
UserNotAuthorized
,
PreferenceValidationError
,
PreferenceUpdateError
,
CountryCodeError
,
)
from
...models
import
UserProfile
,
UserOrgTag
from
...preferences.api
import
(
get_user_preference
,
get_user_preferences
,
set_user_preference
,
update_user_preferences
,
delete_user_preference
,
update_email_opt_in
get_user_preference
,
get_user_preferences
,
set_user_preference
,
update_user_preferences
,
delete_user_preference
,
update_email_opt_in
,
get_country_time_zones
,
)
...
...
@@ -407,7 +418,7 @@ class UpdateEmailOptInTests(ModuleStoreTestCase):
# Set year of birth
user
=
User
.
objects
.
get
(
username
=
self
.
USERNAME
)
profile
=
UserProfile
.
objects
.
get
(
user
=
user
)
year_of_birth
=
datetime
.
datetime
.
now
(
UTC
)
.
year
-
age
year_of_birth
=
datetime
.
datetime
.
now
(
utc
)
.
year
-
age
profile
.
year_of_birth
=
year_of_birth
profile
.
save
()
...
...
@@ -431,6 +442,26 @@ class UpdateEmailOptInTests(ModuleStoreTestCase):
return
True
@ddt.ddt
class
CountryTimeZoneTest
(
TestCase
):
"""
Test cases to validate country code api functionality
"""
@ddt.data
((
'NZ'
,
[
'Pacific/Auckland'
,
'Pacific/Chatham'
]),
(
None
,
common_timezones
))
@ddt.unpack
def
test_get_country_time_zones
(
self
,
country_code
,
expected_time_zones
):
"""Verify that list of common country time zones are returned"""
country_time_zones
=
get_country_time_zones
(
country_code
)
self
.
assertEqual
(
country_time_zones
,
expected_time_zones
)
def
test_country_code_errors
(
self
):
"""Verify that country code error is raised for invalid country code"""
with
self
.
assertRaises
(
CountryCodeError
):
get_country_time_zones
(
'AA'
)
def
get_expected_validation_developer_message
(
preference_key
,
preference_value
):
"""
Returns the expected dict of validation messages for the specified key.
...
...
openedx/core/djangoapps/user_api/serializers.py
View file @
3a65ca02
...
...
@@ -3,6 +3,8 @@ Django REST Framework serializers for the User API application
"""
from
django.contrib.auth.models
import
User
from
rest_framework
import
serializers
from
openedx.core.lib.time_zone_utils
import
get_display_time_zone
from
student.models
import
UserProfile
from
.models
import
UserPreference
...
...
@@ -81,3 +83,23 @@ class ReadOnlyFieldsSerializerMixin(object):
"""
all_fields
=
getattr
(
cls
.
Meta
,
'fields'
,
tuple
())
return
tuple
(
set
(
all_fields
)
-
set
(
cls
.
get_read_only_fields
()))
class
CountryTimeZoneSerializer
(
serializers
.
Serializer
):
# pylint: disable=abstract-method
"""
Serializer that generates a list of common time zones for a country
"""
time_zone
=
serializers
.
SerializerMethodField
()
description
=
serializers
.
SerializerMethodField
()
def
get_time_zone
(
self
,
time_zone_name
):
"""
Returns inputted time zone name
"""
return
time_zone_name
def
get_description
(
self
,
time_zone_name
):
"""
Returns the display version of time zone [e.g. US/Pacific (PST, UTC-0800)]
"""
return
get_display_time_zone
(
time_zone_name
)
openedx/core/djangoapps/user_api/tests/test_views.py
View file @
3a65ca02
...
...
@@ -15,11 +15,12 @@ from django.test.client import RequestFactory
from
django.test.testcases
import
TransactionTestCase
from
django.test.utils
import
override_settings
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
pytz
import
UTC
from
pytz
import
common_timezones_set
,
UTC
from
social.apps.django_app.default.models
import
UserSocialAuth
from
django_comment_common
import
models
from
openedx.core.lib.api.test_utils
import
ApiTestCase
,
TEST_API_KEY
from
openedx.core.lib.time_zone_utils
import
get_display_time_zone
from
openedx.core.djangolib.testing.utils
import
CacheIsolationTestCase
from
student.tests.factories
import
UserFactory
from
third_party_auth.tests.testutil
import
simulate_running_pipeline
,
ThirdPartyAuthTestMixin
...
...
@@ -1963,3 +1964,40 @@ class UpdateEmailOptInTestCase(UserAPITestCase, SharedModuleStoreTestCase):
self
.
assertHttpBadRequest
(
response
)
with
self
.
assertRaises
(
UserOrgTag
.
DoesNotExist
):
UserOrgTag
.
objects
.
get
(
user
=
self
.
user
,
org
=
self
.
course
.
id
.
org
,
key
=
"email-optin"
)
@ddt.ddt
class
CountryTimeZoneListViewTest
(
UserApiTestCase
):
"""
Test cases covering the list viewing behavior for country time zones
"""
ALL_TIME_ZONES_URI
=
"/user_api/v1/preferences/time_zones/"
COUNTRY_TIME_ZONES_URI
=
"/user_api/v1/preferences/time_zones/?country_code=cA"
@ddt.data
(
ALL_TIME_ZONES_URI
,
COUNTRY_TIME_ZONES_URI
)
def
test_options
(
self
,
country_uri
):
""" Verify that following options are allowed """
self
.
assertAllowedMethods
(
country_uri
,
[
'OPTIONS'
,
'GET'
,
'HEAD'
])
@ddt.data
(
ALL_TIME_ZONES_URI
,
COUNTRY_TIME_ZONES_URI
)
def
test_methods_not_allowed
(
self
,
country_uri
):
""" Verify that put, patch, and delete are not allowed """
unallowed_methods
=
[
'put'
,
'patch'
,
'delete'
]
for
unallowed_method
in
unallowed_methods
:
self
.
assertHttpMethodNotAllowed
(
self
.
request_with_auth
(
unallowed_method
,
country_uri
))
def
_assert_time_zone_is_valid
(
self
,
time_zone_info
):
""" Asserts that the time zone is a valid pytz time zone """
time_zone_name
=
time_zone_info
[
'time_zone'
]
self
.
assertIn
(
time_zone_name
,
common_timezones_set
)
self
.
assertEqual
(
time_zone_info
[
'description'
],
get_display_time_zone
(
time_zone_name
))
@ddt.data
((
ALL_TIME_ZONES_URI
,
432
),
(
COUNTRY_TIME_ZONES_URI
,
27
))
@ddt.unpack
def
test_get_basic
(
self
,
country_uri
,
expected_count
):
""" Verify that correct time zone info is returned """
results
=
self
.
get_json
(
country_uri
)
self
.
assertEqual
(
len
(
results
),
expected_count
)
for
time_zone_info
in
results
:
self
.
_assert_time_zone_is_valid
(
time_zone_info
)
openedx/core/djangoapps/user_api/views.py
View file @
3a65ca02
...
...
@@ -31,7 +31,7 @@ from student.cookies import set_logged_in_cookies
from
openedx.core.djangoapps.site_configuration
import
helpers
as
configuration_helpers
from
openedx.core.lib.api.authentication
import
SessionAuthenticationAllowInactiveUser
from
util.json_request
import
JsonResponse
from
.preferences.api
import
update_email_opt_in
from
.preferences.api
import
get_country_time_zones
,
update_email_opt_in
from
.helpers
import
FormDescription
,
shim_student_view
,
require_post_params
from
.models
import
UserPreference
,
UserProfile
from
.accounts
import
(
...
...
@@ -39,7 +39,7 @@ from .accounts import (
USERNAME_MIN_LENGTH
,
USERNAME_MAX_LENGTH
)
from
.accounts.api
import
check_account_exists
from
.serializers
import
UserSerializer
,
UserPreferenceSerializer
from
.serializers
import
CountryTimeZoneSerializer
,
UserSerializer
,
UserPreferenceSerializer
class
LoginSessionView
(
APIView
):
...
...
@@ -1036,3 +1036,37 @@ class UpdateEmailOptInPreference(APIView):
email_opt_in
=
request
.
data
[
'email_opt_in'
]
.
lower
()
==
'true'
update_email_opt_in
(
request
.
user
,
org
,
email_opt_in
)
return
HttpResponse
(
status
=
status
.
HTTP_200_OK
)
class
CountryTimeZoneListView
(
generics
.
ListAPIView
):
"""
**Use Cases**
Retrieves a list of all time zones, by default, or common time zones for country, if given
The country is passed in as its ISO 3166-1 Alpha-2 country code as an
optional 'country_code' argument. The country code is also case-insensitive.
**Example Requests**
GET /user_api/v1/preferences/time_zones/
GET /user_api/v1/preferences/time_zones/?country_code=FR
**Example GET Response**
If the request is successful, an HTTP 200 "OK" response is returned along with a
list of time zone dictionaries for all time zones or just for time zones commonly
used in a country, if given.
Each time zone dictionary contains the following values.
* time_zone: The name of the time zone.
* description: The display version of the time zone
"""
serializer_class
=
CountryTimeZoneSerializer
paginator
=
None
def
get_queryset
(
self
):
country_code
=
self
.
request
.
GET
.
get
(
'country_code'
,
None
)
return
get_country_time_zones
(
country_code
)
openedx/core/lib/tests/test_time_zone_utils.py
View file @
3a65ca02
...
...
@@ -3,7 +3,10 @@ from freezegun import freeze_time
from
student.tests.factories
import
UserFactory
from
openedx.core.djangoapps.user_api.preferences.api
import
set_user_preference
from
openedx.core.lib.time_zone_utils
import
(
get_formatted_time_zone
,
get_time_zone_abbr
,
get_time_zone_offset
,
get_user_time_zone
get_display_time_zone
,
get_time_zone_abbr
,
get_time_zone_offset
,
get_user_time_zone
,
)
from
pytz
import
timezone
,
utc
from
unittest
import
TestCase
...
...
@@ -36,59 +39,59 @@ class TestTimeZoneUtils(TestCase):
user_tz
=
get_user_time_zone
(
self
.
user
)
self
.
assertEqual
(
user_tz
,
timezone
(
'Asia/Tokyo'
))
def
_
formatted
_time_zone_helper
(
self
,
time_zone_string
):
def
_
display
_time_zone_helper
(
self
,
time_zone_string
):
"""
Helper function to return all info from get_
formatted
_time_zone()
Helper function to return all info from get_
display
_time_zone()
"""
tz_str
=
get_display_time_zone
(
time_zone_string
)
time_zone
=
timezone
(
time_zone_string
)
tz_str
=
get_formatted_time_zone
(
time_zone
)
tz_abbr
=
get_time_zone_abbr
(
time_zone
)
tz_offset
=
get_time_zone_offset
(
time_zone
)
return
{
'str'
:
tz_str
,
'abbr'
:
tz_abbr
,
'offset'
:
tz_offset
}
def
_assert_time_zone_info_equal
(
self
,
formatted
_tz_info
,
expected_name
,
expected_abbr
,
expected_offset
):
def
_assert_time_zone_info_equal
(
self
,
display
_tz_info
,
expected_name
,
expected_abbr
,
expected_offset
):
"""
Asserts that all
formatted
_tz_info is equal to the expected inputs
Asserts that all
display
_tz_info is equal to the expected inputs
"""
self
.
assertEqual
(
formatted
_tz_info
[
'str'
],
'{name} ({abbr}, UTC{offset})'
.
format
(
name
=
expected_name
,
abbr
=
expected_abbr
,
offset
=
expected_offset
))
self
.
assertEqual
(
formatted
_tz_info
[
'abbr'
],
expected_abbr
)
self
.
assertEqual
(
formatted
_tz_info
[
'offset'
],
expected_offset
)
self
.
assertEqual
(
display
_tz_info
[
'str'
],
'{name} ({abbr}, UTC{offset})'
.
format
(
name
=
expected_name
,
abbr
=
expected_abbr
,
offset
=
expected_offset
))
self
.
assertEqual
(
display
_tz_info
[
'abbr'
],
expected_abbr
)
self
.
assertEqual
(
display
_tz_info
[
'offset'
],
expected_offset
)
@freeze_time
(
"2015-02-09"
)
def
test_
formatted
_time_zone_without_dst
(
self
):
def
test_
display
_time_zone_without_dst
(
self
):
"""
Test to ensure get_
formatted_time_zone() returns full formatted
string when no kwargs specified
Test to ensure get_
display_time_zone() returns full display
string when no kwargs specified
and returns just abbreviation or offset when specified
"""
tz_info
=
self
.
_
formatted
_time_zone_helper
(
'America/Los_Angeles'
)
tz_info
=
self
.
_
display
_time_zone_helper
(
'America/Los_Angeles'
)
self
.
_assert_time_zone_info_equal
(
tz_info
,
'America/Los Angeles'
,
'PST'
,
'-0800'
)
@freeze_time
(
"2015-04-02"
)
def
test_
formatted
_time_zone_with_dst
(
self
):
def
test_
display
_time_zone_with_dst
(
self
):
"""
Test to ensure get_
formatted
_time_zone() returns modified abbreviations and
Test to ensure get_
display
_time_zone() returns modified abbreviations and
offsets during daylight savings time.
"""
tz_info
=
self
.
_
formatted
_time_zone_helper
(
'America/Los_Angeles'
)
tz_info
=
self
.
_
display
_time_zone_helper
(
'America/Los_Angeles'
)
self
.
_assert_time_zone_info_equal
(
tz_info
,
'America/Los Angeles'
,
'PDT'
,
'-0700'
)
@freeze_time
(
"2015-11-01 08:59:00"
)
def
test_
formatted
_time_zone_ambiguous_before
(
self
):
def
test_
display
_time_zone_ambiguous_before
(
self
):
"""
Test to ensure get_
formatted
_time_zone() returns correct abbreviations and offsets
Test to ensure get_
display
_time_zone() returns correct abbreviations and offsets
during ambiguous time periods (e.g. when DST is about to start/end) before the change
"""
tz_info
=
self
.
_
formatted
_time_zone_helper
(
'America/Los_Angeles'
)
tz_info
=
self
.
_
display
_time_zone_helper
(
'America/Los_Angeles'
)
self
.
_assert_time_zone_info_equal
(
tz_info
,
'America/Los Angeles'
,
'PDT'
,
'-0700'
)
@freeze_time
(
"2015-11-01 09:00:00"
)
def
test_
formatted
_time_zone_ambiguous_after
(
self
):
def
test_
display
_time_zone_ambiguous_after
(
self
):
"""
Test to ensure get_
formatted
_time_zone() returns correct abbreviations and offsets
Test to ensure get_
display
_time_zone() returns correct abbreviations and offsets
during ambiguous time periods (e.g. when DST is about to start/end) after the change
"""
tz_info
=
self
.
_
formatted
_time_zone_helper
(
'America/Los_Angeles'
)
tz_info
=
self
.
_
display
_time_zone_helper
(
'America/Los_Angeles'
)
self
.
_assert_time_zone_info_equal
(
tz_info
,
'America/Los Angeles'
,
'PST'
,
'-0800'
)
openedx/core/lib/time_zone_utils.py
View file @
3a65ca02
...
...
@@ -43,12 +43,13 @@ def get_time_zone_offset(time_zone, date_time=None):
return
_format_time_zone_string
(
time_zone
,
date_time
,
'
%
z'
)
def
get_
formatted_time_zone
(
time_zon
e
):
def
get_
display_time_zone
(
time_zone_nam
e
):
"""
Returns a formatted time zone (e.g. 'Asia/Tokyo (JST, UTC+0900)')
Returns a formatted
display
time zone (e.g. 'Asia/Tokyo (JST, UTC+0900)')
:param time_zone
: Pytz time zone object
:param time_zone
_name (str): Name of Pytz time zone
"""
time_zone
=
timezone
(
time_zone_name
)
tz_abbr
=
get_time_zone_abbr
(
time_zone
)
tz_offset
=
get_time_zone_offset
(
time_zone
)
...
...
@@ -56,6 +57,6 @@ def get_formatted_time_zone(time_zone):
TIME_ZONE_CHOICES
=
sorted
(
[(
tz
,
get_
formatted_time_zone
(
timezone
(
tz
)
))
for
tz
in
common_timezones
],
[(
tz
,
get_
display_time_zone
(
tz
))
for
tz
in
common_timezones
],
key
=
lambda
tz_tuple
:
tz_tuple
[
1
]
)
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