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
127095b9
Commit
127095b9
authored
Jul 18, 2016
by
Kevin Kim
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add endpoint for commonly used country time zones
parent
194c5096
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 @
127095b9
...
...
@@ -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 @
127095b9
...
...
@@ -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 @
127095b9
...
...
@@ -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 @
127095b9
...
...
@@ -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 @
127095b9
...
...
@@ -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 @
127095b9
...
...
@@ -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 @
127095b9
...
...
@@ -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 @
127095b9
...
...
@@ -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 @
127095b9
...
...
@@ -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