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
e710a5b2
Commit
e710a5b2
authored
Mar 20, 2015
by
Andy Armstrong
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implement parental controls for the User API
TNL-1739
parent
650a9a9b
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
61 additions
and
29 deletions
+61
-29
openedx/core/djangoapps/user_api/accounts/api.py
+14
-7
openedx/core/djangoapps/user_api/accounts/serializers.py
+13
-8
openedx/core/djangoapps/user_api/accounts/tests/test_api.py
+1
-0
openedx/core/djangoapps/user_api/accounts/tests/test_views.py
+15
-11
openedx/core/djangoapps/user_api/accounts/views.py
+18
-3
No files found.
openedx/core/djangoapps/user_api/accounts/api.py
View file @
e710a5b2
...
...
@@ -19,7 +19,7 @@ from ..helpers import intercept_errors
from
..models
import
UserPreference
from
.
import
(
ACCOUNT_VISIBILITY_PREF_KEY
,
ALL_USERS_VISIBILITY
,
ACCOUNT_VISIBILITY_PREF_KEY
,
ALL_USERS_VISIBILITY
,
PRIVATE_VISIBILITY
,
EMAIL_MIN_LENGTH
,
EMAIL_MAX_LENGTH
,
PASSWORD_MIN_LENGTH
,
PASSWORD_MAX_LENGTH
,
USERNAME_MIN_LENGTH
,
USERNAME_MAX_LENGTH
)
...
...
@@ -76,12 +76,8 @@ def get_account_settings(requesting_user, username=None, configuration=None, vie
visible_settings
=
{}
# Calling UserPreference directly because the requesting user may be different from existing_user
# (and does not have to be is_staff).
profile_privacy
=
UserPreference
.
get_value
(
existing_user
,
ACCOUNT_VISIBILITY_PREF_KEY
)
privacy_setting
=
profile_privacy
if
profile_privacy
else
configuration
.
get
(
'default_visibility'
)
if
privacy_setting
==
ALL_USERS_VISIBILITY
:
profile_visibility
=
_get_profile_visibility
(
existing_user_profile
,
configuration
)
if
profile_visibility
==
ALL_USERS_VISIBILITY
:
field_names
=
configuration
.
get
(
'shareable_fields'
)
else
:
field_names
=
configuration
.
get
(
'public_fields'
)
...
...
@@ -92,6 +88,17 @@ def get_account_settings(requesting_user, username=None, configuration=None, vie
return
visible_settings
def
_get_profile_visibility
(
user_profile
,
configuration
):
"""Returns the visibility level for the specified user profile."""
if
user_profile
.
requires_parental_consent
():
return
PRIVATE_VISIBILITY
# Calling UserPreference directly because the requesting user may be different from existing_user
# (and does not have to be is_staff).
profile_privacy
=
UserPreference
.
get_value
(
user_profile
.
user
,
ACCOUNT_VISIBILITY_PREF_KEY
)
return
profile_privacy
if
profile_privacy
else
configuration
.
get
(
'default_visibility'
)
@intercept_errors
(
UserAPIInternalError
,
ignore_errors
=
[
UserAPIRequestError
])
def
update_account_settings
(
requesting_user
,
update
,
username
=
None
):
"""Update user account information.
...
...
openedx/core/djangoapps/user_api/accounts/serializers.py
View file @
e710a5b2
...
...
@@ -25,16 +25,17 @@ class AccountLegacyProfileSerializer(serializers.HyperlinkedModelSerializer, Rea
Class that serializes the portion of UserProfile model needed for account information.
"""
profile_image
=
serializers
.
SerializerMethodField
(
"get_profile_image"
)
requires_parental_consent
=
serializers
.
SerializerMethodField
(
"get_requires_parental_consent"
)
class
Meta
:
model
=
UserProfile
fields
=
(
"name"
,
"gender"
,
"goals"
,
"year_of_birth"
,
"level_of_education"
,
"language"
,
"country"
,
"mailing_address"
,
"bio"
,
"profile_image"
"mailing_address"
,
"bio"
,
"profile_image"
,
"requires_parental_consent"
,
)
# Currently no read-only field, but keep this so view code doesn't need to know.
read_only_fields
=
()
explicit_read_only_fields
=
(
"profile_image"
,)
explicit_read_only_fields
=
(
"profile_image"
,
"requires_parental_consent"
)
def
validate_name
(
self
,
attrs
,
source
):
""" Enforce minimum length for name. """
...
...
@@ -48,15 +49,15 @@ class AccountLegacyProfileSerializer(serializers.HyperlinkedModelSerializer, Rea
return
attrs
def
transform_gender
(
self
,
obj
,
value
):
def
transform_gender
(
self
,
user_profile
,
value
):
""" Converts empty string to None, to indicate not set. Replaced by to_representation in version 3. """
return
AccountLegacyProfileSerializer
.
convert_empty_to_None
(
value
)
def
transform_country
(
self
,
obj
,
value
):
def
transform_country
(
self
,
user_profile
,
value
):
""" Converts empty string to None, to indicate not set. Replaced by to_representation in version 3. """
return
AccountLegacyProfileSerializer
.
convert_empty_to_None
(
value
)
def
transform_level_of_education
(
self
,
obj
,
value
):
def
transform_level_of_education
(
self
,
user_profile
,
value
):
""" Converts empty string to None, to indicate not set. Replaced by to_representation in version 3. """
return
AccountLegacyProfileSerializer
.
convert_empty_to_None
(
value
)
...
...
@@ -65,12 +66,16 @@ class AccountLegacyProfileSerializer(serializers.HyperlinkedModelSerializer, Rea
""" Helper method to convert empty string to None (other values pass through). """
return
None
if
value
==
""
else
value
def
get_profile_image
(
self
,
obj
):
def
get_profile_image
(
self
,
user_profile
):
""" Returns metadata about a user's profile image. """
data
=
{
'has_image'
:
obj
.
has_profile_image
}
data
=
{
'has_image'
:
user_profile
.
has_profile_image
}
data
.
update
({
'{image_key_prefix}_{size}'
.
format
(
image_key_prefix
=
PROFILE_IMAGE_KEY_PREFIX
,
size
=
size_display_name
):
get_profile_image_url_for_user
(
obj
.
user
,
size_value
)
get_profile_image_url_for_user
(
user_profile
.
user
,
size_value
)
for
size_display_name
,
size_value
in
PROFILE_IMAGE_SIZES_MAP
.
items
()
})
return
data
def
get_requires_parental_consent
(
self
,
user_profile
):
""" Returns a boolean representing whether the user requires parental controls. """
return
user_profile
.
requires_parental_consent
()
openedx/core/djangoapps/user_api/accounts/tests/test_api.py
View file @
e710a5b2
...
...
@@ -220,6 +220,7 @@ class AccountSettingsOnCreationTest(TestCase):
'image_url_full'
:
'http://example-storage.com/profile_images/default_50.jpg'
,
'image_url_small'
:
'http://example-storage.com/profile_images/default_10.jpg'
,
},
'requires_parental_consent'
:
True
,
})
...
...
openedx/core/djangoapps/user_api/accounts/tests/test_views.py
View file @
e710a5b2
# -*- coding: utf-8 -*-
import
datetime
import
ddt
import
hashlib
import
json
...
...
@@ -84,9 +85,10 @@ class UserAPITestCase(APITestCase):
legacy_profile
=
UserProfile
.
objects
.
get
(
id
=
user
.
id
)
legacy_profile
.
country
=
"US"
legacy_profile
.
level_of_education
=
"m"
legacy_profile
.
year_of_birth
=
19
00
legacy_profile
.
year_of_birth
=
20
00
legacy_profile
.
goals
=
"world peace"
legacy_profile
.
mailing_address
=
"Park Ave"
legacy_profile
.
gender
=
"f"
legacy_profile
.
bio
=
"Tired mother of twins"
legacy_profile
.
has_profile_image
=
True
legacy_profile
.
save
()
...
...
@@ -139,27 +141,27 @@ class TestAccountAPI(UserAPITestCase):
self
.
assertIsNone
(
data
[
"languages"
])
self
.
assertEqual
(
"Tired mother of twins"
,
data
[
"bio"
])
def
_verify_private_account_response
(
self
,
response
):
def
_verify_private_account_response
(
self
,
response
,
requires_parental_consent
=
False
):
"""
Verify that only the public fields are returned if a user does not want to share account fields
"""
data
=
response
.
data
self
.
assertEqual
(
2
,
len
(
data
))
self
.
assertEqual
(
self
.
user
.
username
,
data
[
"username"
])
self
.
_verify_profile_image_data
(
data
,
True
)
self
.
_verify_profile_image_data
(
data
,
not
requires_parental_consent
)
def
_verify_full_account_response
(
self
,
response
):
def
_verify_full_account_response
(
self
,
response
,
requires_parental_consent
=
False
):
"""
Verify that all account fields are returned (even those that are not shareable).
"""
data
=
response
.
data
self
.
assertEqual
(
1
4
,
len
(
data
))
self
.
assertEqual
(
1
5
,
len
(
data
))
self
.
assertEqual
(
self
.
user
.
username
,
data
[
"username"
])
self
.
assertEqual
(
self
.
user
.
first_name
+
" "
+
self
.
user
.
last_name
,
data
[
"name"
])
self
.
assertEqual
(
"US"
,
data
[
"country"
])
self
.
assertEqual
(
""
,
data
[
"language"
])
self
.
assertEqual
(
"
m
"
,
data
[
"gender"
])
self
.
assertEqual
(
19
00
,
data
[
"year_of_birth"
])
self
.
assertEqual
(
"
f
"
,
data
[
"gender"
])
self
.
assertEqual
(
20
00
,
data
[
"year_of_birth"
])
self
.
assertEqual
(
"m"
,
data
[
"level_of_education"
])
self
.
assertEqual
(
"world peace"
,
data
[
"goals"
])
self
.
assertEqual
(
"Park Ave"
,
data
[
'mailing_address'
])
...
...
@@ -167,7 +169,8 @@ class TestAccountAPI(UserAPITestCase):
self
.
assertTrue
(
data
[
"is_active"
])
self
.
assertIsNotNone
(
data
[
"date_joined"
])
self
.
assertEqual
(
"Tired mother of twins"
,
data
[
"bio"
])
self
.
_verify_profile_image_data
(
data
,
True
)
self
.
_verify_profile_image_data
(
data
,
not
requires_parental_consent
)
self
.
assertEquals
(
requires_parental_consent
,
data
[
"requires_parental_consent"
])
def
test_anonymous_access
(
self
):
"""
...
...
@@ -269,7 +272,7 @@ class TestAccountAPI(UserAPITestCase):
def
verify_get_own_information
():
response
=
self
.
send_get
(
self
.
client
)
data
=
response
.
data
self
.
assertEqual
(
1
4
,
len
(
data
))
self
.
assertEqual
(
1
5
,
len
(
data
))
self
.
assertEqual
(
self
.
user
.
username
,
data
[
"username"
])
self
.
assertEqual
(
self
.
user
.
first_name
+
" "
+
self
.
user
.
last_name
,
data
[
"name"
])
for
empty_field
in
(
"year_of_birth"
,
"level_of_education"
,
"mailing_address"
,
"bio"
):
...
...
@@ -283,6 +286,7 @@ class TestAccountAPI(UserAPITestCase):
self
.
assertIsNotNone
(
data
[
"date_joined"
])
self
.
assertEqual
(
self
.
user
.
is_active
,
data
[
"is_active"
])
self
.
_verify_profile_image_data
(
data
,
False
)
self
.
assertTrue
(
data
[
"requires_parental_consent"
])
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
self
.
test_password
)
verify_get_own_information
()
...
...
@@ -406,8 +410,8 @@ class TestAccountAPI(UserAPITestCase):
"Field '{0}' cannot be edited."
.
format
(
field_name
),
data
[
"field_errors"
][
field_name
][
"user_message"
]
)
for
field_name
in
[
"username"
,
"date_joined"
,
"is_active"
]:
response
=
self
.
send_patch
(
client
,
{
field_name
:
"will_error"
,
"gender"
:
"
f
"
},
expected_status
=
400
)
for
field_name
in
[
"username"
,
"date_joined"
,
"is_active"
,
"profile_image"
,
"requires_parental_consent"
]:
response
=
self
.
send_patch
(
client
,
{
field_name
:
"will_error"
,
"gender"
:
"
o
"
},
expected_status
=
400
)
verify_error_response
(
field_name
,
response
.
data
)
# Make sure that gender did not change.
...
...
openedx/core/djangoapps/user_api/accounts/views.py
View file @
e710a5b2
...
...
@@ -80,9 +80,24 @@ class AccountView(APIView):
* goals: The textual representation of the user's goals, or null.
* bio: null or textural representation of user biographical
information ("about me")
For all text fields, clients rendering the values should take care
information ("about me").
* profile_image: a dict with the following keys describing
the user's profile image:
* "has_image": true if the user has a profile image
* "image_url_full": an absolute URL to the user's full
profile image
* "image_url_large": an absolute URL to a large thumbnail
of the profile image
* "image_url_medium": an absolute URL to a medium thumbnail
of the profile image
* "image_url_small": an absolute URL to a small thumbnail
of the profile image
* requires_parental_consent: true if the user is a minor
requiring parental consent.
> For all text fields, clients rendering the values should take care
to HTML escape them to avoid script injections, as the data is
stored exactly as specified. The intention is that plain text is
supported, not HTML.
...
...
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