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
7742735f
Commit
7742735f
authored
Apr 14, 2015
by
Andy Armstrong
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Event on user preference updates
TNL-1851
parent
1f3d2c24
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
360 additions
and
99 deletions
+360
-99
common/djangoapps/util/model_utils.py
+58
-14
common/test/acceptance/pages/lms/learner_profile.py
+3
-2
common/test/acceptance/tests/lms/test_account_settings.py
+65
-52
common/test/acceptance/tests/lms/test_learner_profile.py
+99
-20
openedx/core/djangoapps/user_api/accounts/api.py
+8
-10
openedx/core/djangoapps/user_api/models.py
+37
-0
openedx/core/djangoapps/user_api/tests/test_models.py
+90
-1
No files found.
common/djangoapps/util/model_utils.py
View file @
7742735f
...
@@ -3,6 +3,7 @@ Utilities for django models.
...
@@ -3,6 +3,7 @@ Utilities for django models.
"""
"""
from
eventtracking
import
tracker
from
eventtracking
import
tracker
from
django.conf
import
settings
from
django.core.exceptions
import
ObjectDoesNotExist
from
django.core.exceptions
import
ObjectDoesNotExist
from
django.db.models.fields.related
import
RelatedField
from
django.db.models.fields.related
import
RelatedField
...
@@ -46,9 +47,7 @@ def get_changed_fields_dict(instance, model_class):
...
@@ -46,9 +47,7 @@ def get_changed_fields_dict(instance, model_class):
def
emit_field_changed_events
(
instance
,
user
,
event_name
,
db_table
,
excluded_fields
=
None
,
hidden_fields
=
None
):
def
emit_field_changed_events
(
instance
,
user
,
event_name
,
db_table
,
excluded_fields
=
None
,
hidden_fields
=
None
):
"""
"""Emits an event for each field that has changed.
For the given model instance, emit a setting changed event the fields that
have changed since the last save.
Note that this function expects that a `_changed_fields` dict has been set
Note that this function expects that a `_changed_fields` dict has been set
as an attribute on `instance` (see `get_changed_fields_dict`.
as an attribute on `instance` (see `get_changed_fields_dict`.
...
@@ -87,16 +86,61 @@ def emit_field_changed_events(instance, user, event_name, db_table, excluded_fie
...
@@ -87,16 +86,61 @@ def emit_field_changed_events(instance, user, event_name, db_table, excluded_fie
changed_fields
=
getattr
(
instance
,
'_changed_fields'
,
{})
changed_fields
=
getattr
(
instance
,
'_changed_fields'
,
{})
for
field_name
in
changed_fields
:
for
field_name
in
changed_fields
:
if
field_name
not
in
excluded_fields
:
if
field_name
not
in
excluded_fields
:
tracker
.
emit
(
old_value
=
clean_field
(
field_name
,
changed_fields
[
field_name
])
event_name
,
new_value
=
clean_field
(
field_name
,
getattr
(
instance
,
field_name
))
{
emit_setting_changed_event
(
user
,
event_name
,
db_table
,
field_name
,
old_value
,
new_value
)
"setting"
:
field_name
,
'old'
:
clean_field
(
field_name
,
changed_fields
[
field_name
]),
'new'
:
clean_field
(
field_name
,
getattr
(
instance
,
field_name
)),
"user_id"
:
user
.
id
,
"table"
:
db_table
}
)
# Remove the now inaccurate _changed_fields attribute.
# Remove the now inaccurate _changed_fields attribute.
if
getattr
(
instance
,
'_changed_fields'
,
None
):
if
hasattr
(
instance
,
'_changed_fields'
):
del
instance
.
_changed_fields
del
instance
.
_changed_fields
def
emit_setting_changed_event
(
user
,
event_name
,
db_table
,
setting_name
,
old_value
,
new_value
):
"""Emits an event for a change in a setting.
Args:
user (User): the user that this setting is associated with.
event_name (str): the name of the event to be emitted.
db_table (str): the name of the table that we're modifying.
setting_name (str): the name of the setting being changed.
old_value (object): the value before the change.
new_value (object): the new value being saved.
Returns:
None
"""
# Compute the maximum value length so that two copies can fit into the maximum event size
# in addition to all the other fields recorded.
max_value_length
=
settings
.
TRACK_MAX_EVENT
/
4
serialized_old_value
,
old_was_truncated
=
_get_truncated_setting_value
(
old_value
,
max_length
=
max_value_length
)
serialized_new_value
,
new_was_truncated
=
_get_truncated_setting_value
(
new_value
,
max_length
=
max_value_length
)
truncated_values
=
[]
if
old_was_truncated
:
truncated_values
.
append
(
"old"
)
if
new_was_truncated
:
truncated_values
.
append
(
"new"
)
tracker
.
emit
(
event_name
,
{
"setting"
:
setting_name
,
"old"
:
serialized_old_value
,
"new"
:
serialized_new_value
,
"truncated"
:
truncated_values
,
"user_id"
:
user
.
id
,
"table"
:
db_table
,
}
)
def
_get_truncated_setting_value
(
value
,
max_length
=
None
):
"""
Returns the truncated form of a setting value.
Returns:
truncated_value (object): the possibly truncated version of the value.
was_truncated (bool): returns true if the serialized value was truncated.
"""
if
isinstance
(
value
,
basestring
)
and
max_length
is
not
None
and
len
(
value
)
>
max_length
:
return
value
[
0
:
max_length
],
True
else
:
return
value
,
False
common/test/acceptance/pages/lms/learner_profile.py
View file @
7742735f
...
@@ -201,11 +201,12 @@ class LearnerProfilePage(FieldsMixin, PageObject):
...
@@ -201,11 +201,12 @@ class LearnerProfilePage(FieldsMixin, PageObject):
self
.
wait_for_field
(
'image'
)
self
.
wait_for_field
(
'image'
)
return
self
.
q
(
css
=
'.u-field-upload-button'
)
.
visible
return
self
.
q
(
css
=
'.u-field-upload-button'
)
.
visible
def
upload_file
(
self
,
filename
):
def
upload_file
(
self
,
filename
,
wait_for_upload_button
=
True
):
"""
"""
Helper method to upload an image file.
Helper method to upload an image file.
"""
"""
self
.
wait_for_element_visibility
(
'.u-field-upload-button'
,
"upload button is visible"
)
if
wait_for_upload_button
:
self
.
wait_for_element_visibility
(
'.u-field-upload-button'
,
"upload button is visible"
)
file_path
=
InstructorDashboardPage
.
get_asset_path
(
filename
)
file_path
=
InstructorDashboardPage
.
get_asset_path
(
filename
)
# make the elements visible.
# make the elements visible.
...
...
common/test/acceptance/tests/lms/test_account_settings.py
View file @
7742735f
...
@@ -47,8 +47,10 @@ class AccountSettingsTestMixin(EventsTestMixin, WebAppTest):
...
@@ -47,8 +47,10 @@ class AccountSettingsTestMixin(EventsTestMixin, WebAppTest):
"""
"""
expected_referers
=
[
self
.
ACCOUNT_SETTINGS_REFERER
]
*
len
(
events
)
expected_referers
=
[
self
.
ACCOUNT_SETTINGS_REFERER
]
*
len
(
events
)
for
event
in
events
:
for
event
in
events
:
event
[
u'user_id'
]
=
long
(
user_id
)
event
[
u"user_id"
]
=
long
(
user_id
)
event
[
u'table'
]
=
u"auth_userprofile"
if
table
is
None
else
table
event
[
u"table"
]
=
u"auth_userprofile"
if
table
is
None
else
table
event
[
u"truncated"
]
=
[]
self
.
verify_events_of_type
(
self
.
verify_events_of_type
(
username
,
self
.
USER_SETTINGS_CHANGED_EVENT_NAME
,
events
,
username
,
self
.
USER_SETTINGS_CHANGED_EVENT_NAME
,
events
,
expected_referers
=
expected_referers
expected_referers
=
expected_referers
...
@@ -233,16 +235,18 @@ class AccountSettingsPageTest(AccountSettingsTestMixin, WebAppTest):
...
@@ -233,16 +235,18 @@ class AccountSettingsPageTest(AccountSettingsTestMixin, WebAppTest):
self
.
verify_settings_changed_events
(
self
.
verify_settings_changed_events
(
self
.
username
,
self
.
user_id
,
self
.
username
,
self
.
user_id
,
[{
[
u"setting"
:
u"name"
,
{
u"old"
:
self
.
username
,
u"setting"
:
u"name"
,
u"new"
:
u"another name"
,
u"old"
:
self
.
username
,
},
u"new"
:
u"another name"
,
{
},
u"setting"
:
u"name"
,
{
u"old"
:
u'another name'
,
u"setting"
:
u"name"
,
u"new"
:
self
.
username
,
u"old"
:
u'another name'
,
}]
u"new"
:
self
.
username
,
}
]
)
)
def
test_email_field
(
self
):
def
test_email_field
(
self
):
...
@@ -339,16 +343,18 @@ class AccountSettingsPageTest(AccountSettingsTestMixin, WebAppTest):
...
@@ -339,16 +343,18 @@ class AccountSettingsPageTest(AccountSettingsTestMixin, WebAppTest):
)
)
self
.
verify_settings_changed_events
(
self
.
verify_settings_changed_events
(
self
.
username
,
self
.
user_id
,
self
.
username
,
self
.
user_id
,
[{
[
u"setting"
:
u"level_of_education"
,
{
u"old"
:
None
,
u"setting"
:
u"level_of_education"
,
u"new"
:
u'b'
,
u"old"
:
None
,
},
u"new"
:
u'b'
,
{
},
u"setting"
:
u"level_of_education"
,
{
u"old"
:
u'b'
,
u"setting"
:
u"level_of_education"
,
u"new"
:
None
,
u"old"
:
u'b'
,
}]
u"new"
:
None
,
}
]
)
)
def
test_gender_field
(
self
):
def
test_gender_field
(
self
):
...
@@ -363,16 +369,18 @@ class AccountSettingsPageTest(AccountSettingsTestMixin, WebAppTest):
...
@@ -363,16 +369,18 @@ class AccountSettingsPageTest(AccountSettingsTestMixin, WebAppTest):
)
)
self
.
verify_settings_changed_events
(
self
.
verify_settings_changed_events
(
self
.
username
,
self
.
user_id
,
self
.
username
,
self
.
user_id
,
[{
[
u"setting"
:
u"gender"
,
{
u"old"
:
None
,
u"setting"
:
u"gender"
,
u"new"
:
u'f'
,
u"old"
:
None
,
},
u"new"
:
u'f'
,
{
},
u"setting"
:
u"gender"
,
{
u"old"
:
u'f'
,
u"setting"
:
u"gender"
,
u"new"
:
None
,
u"old"
:
u'f'
,
}]
u"new"
:
None
,
}
]
)
)
def
test_year_of_birth_field
(
self
):
def
test_year_of_birth_field
(
self
):
...
@@ -390,16 +398,18 @@ class AccountSettingsPageTest(AccountSettingsTestMixin, WebAppTest):
...
@@ -390,16 +398,18 @@ class AccountSettingsPageTest(AccountSettingsTestMixin, WebAppTest):
)
)
self
.
verify_settings_changed_events
(
self
.
verify_settings_changed_events
(
self
.
username
,
self
.
user_id
,
self
.
username
,
self
.
user_id
,
[{
[
u"setting"
:
u"year_of_birth"
,
{
u"old"
:
None
,
u"setting"
:
u"year_of_birth"
,
u"new"
:
1980
,
u"old"
:
None
,
},
u"new"
:
1980L
,
{
},
u"setting"
:
u"year_of_birth"
,
{
u"old"
:
1980
,
u"setting"
:
u"year_of_birth"
,
u"new"
:
None
,
u"old"
:
1980L
,
}]
u"new"
:
None
,
}
]
)
)
def
test_country_field
(
self
):
def
test_country_field
(
self
):
...
@@ -441,16 +451,19 @@ class AccountSettingsPageTest(AccountSettingsTestMixin, WebAppTest):
...
@@ -441,16 +451,19 @@ class AccountSettingsPageTest(AccountSettingsTestMixin, WebAppTest):
)
)
self
.
verify_settings_changed_events
(
self
.
verify_settings_changed_events
(
[{
self
.
username
,
self
.
user_id
,
u"setting"
:
u"language_proficiencies"
,
[
u"old"
:
[],
{
u"new"
:
[{
u"code"
:
u"ps"
}],
u"setting"
:
u"language_proficiencies"
,
},
u"old"
:
[],
{
u"new"
:
[{
u"code"
:
u"ps"
}],
u"setting"
:
u"language_proficiencies"
,
},
u"old"
:
[{
u"code"
:
u"ps"
}],
{
u"new"
:
[],
u"setting"
:
u"language_proficiencies"
,
}],
u"old"
:
[{
u"code"
:
u"ps"
}],
u"new"
:
[],
}
],
table
=
u"student_languageproficiency"
table
=
u"student_languageproficiency"
)
)
...
...
common/test/acceptance/tests/lms/test_learner_profile.py
View file @
7742735f
...
@@ -19,8 +19,8 @@ class LearnerProfileTestMixin(EventsTestMixin):
...
@@ -19,8 +19,8 @@ class LearnerProfileTestMixin(EventsTestMixin):
Mixin with helper methods for testing learner profile pages.
Mixin with helper methods for testing learner profile pages.
"""
"""
PRIVACY_PUBLIC
=
'all_users'
PRIVACY_PUBLIC
=
u
'all_users'
PRIVACY_PRIVATE
=
'private'
PRIVACY_PRIVATE
=
u
'private'
PUBLIC_PROFILE_FIELDS
=
[
'username'
,
'country'
,
'language_proficiencies'
,
'bio'
]
PUBLIC_PROFILE_FIELDS
=
[
'username'
,
'country'
,
'language_proficiencies'
,
'bio'
]
PRIVATE_PROFILE_FIELDS
=
[
'username'
]
PRIVATE_PROFILE_FIELDS
=
[
'username'
]
...
@@ -84,6 +84,27 @@ class LearnerProfileTestMixin(EventsTestMixin):
...
@@ -84,6 +84,27 @@ class LearnerProfileTestMixin(EventsTestMixin):
str
(
birth_year
)
str
(
birth_year
)
)
)
def
verify_profile_page_is_public
(
self
,
profile_page
,
is_editable
=
True
):
"""
Verify that the profile page is currently public.
:return:
"""
self
.
assertEqual
(
profile_page
.
visible_fields
,
self
.
PUBLIC_PROFILE_FIELDS
)
if
is_editable
:
self
.
assertTrue
(
profile_page
.
privacy_field_visible
)
self
.
assertEqual
(
profile_page
.
editable_fields
,
self
.
PUBLIC_PROFILE_EDITABLE_FIELDS
)
else
:
self
.
assertEqual
(
profile_page
.
editable_fields
,
[])
def
verify_profile_page_is_private
(
self
,
profile_page
,
is_editable
=
True
):
"""
Verify that the profile page is currently private.
:return:
"""
if
is_editable
:
self
.
assertTrue
(
profile_page
.
privacy_field_visible
)
self
.
assertEqual
(
profile_page
.
visible_fields
,
self
.
PRIVATE_PROFILE_FIELDS
)
def
verify_profile_page_view_event
(
self
,
requesting_username
,
profile_user_id
,
visibility
=
None
):
def
verify_profile_page_view_event
(
self
,
requesting_username
,
profile_user_id
,
visibility
=
None
):
"""
"""
Verifies that the correct view event was captured for the profile page.
Verifies that the correct view event was captured for the profile page.
...
@@ -108,6 +129,24 @@ class LearnerProfileTestMixin(EventsTestMixin):
...
@@ -108,6 +129,24 @@ class LearnerProfileTestMixin(EventsTestMixin):
self
.
USER_SETTINGS_CHANGED_EVENT_NAME
,
self
.
start_time
,
profile_user_id
,
num_times
,
setting
=
setting
self
.
USER_SETTINGS_CHANGED_EVENT_NAME
,
self
.
start_time
,
profile_user_id
,
num_times
,
setting
=
setting
)
)
def
verify_user_preference_changed_event
(
self
,
username
,
user_id
,
setting
,
old_value
=
None
,
new_value
=
None
):
"""
Verifies that the correct user preference changed event was recorded.
"""
self
.
verify_events_of_type
(
username
,
u"edx.user.settings.changed"
,
[{
u"user_id"
:
long
(
user_id
),
u"table"
:
u"user_api_userpreference"
,
u"setting"
:
unicode
(
setting
),
u"old"
:
old_value
,
u"new"
:
new_value
,
u"truncated"
:
[],
}],
expected_referers
=
[
"/u/{username}"
.
format
(
username
=
username
)],
)
class
OwnLearnerProfilePageTest
(
LearnerProfileTestMixin
,
WebAppTest
):
class
OwnLearnerProfilePageTest
(
LearnerProfileTestMixin
,
WebAppTest
):
"""
"""
...
@@ -144,6 +183,56 @@ class OwnLearnerProfilePageTest(LearnerProfileTestMixin, WebAppTest):
...
@@ -144,6 +183,56 @@ class OwnLearnerProfilePageTest(LearnerProfileTestMixin, WebAppTest):
self
.
assertTrue
(
profile_page
.
profile_has_default_image
)
self
.
assertTrue
(
profile_page
.
profile_has_default_image
)
self
.
assertTrue
(
profile_page
.
profile_has_image_with_public_access
())
self
.
assertTrue
(
profile_page
.
profile_has_image_with_public_access
())
def
test_make_profile_public
(
self
):
"""
Scenario: Verify that the user can change their privacy.
Given that I am a registered user
And I visit my private profile page
And I set the profile visibility to public
Then a user preference changed event should be recorded
When I reload the page
Then the profile visibility should be shown as public
"""
username
,
user_id
=
self
.
log_in_as_unique_user
()
profile_page
=
self
.
visit_profile_page
(
username
,
privacy
=
self
.
PRIVACY_PRIVATE
)
profile_page
.
privacy
=
self
.
PRIVACY_PUBLIC
self
.
verify_user_preference_changed_event
(
username
,
user_id
,
"account_privacy"
,
old_value
=
None
,
# Note: no old value as the default preference is private
new_value
=
self
.
PRIVACY_PUBLIC
,
)
# Reload the page and verify that the profile is now public
self
.
browser
.
refresh
()
profile_page
.
wait_for_page
()
self
.
verify_profile_page_is_public
(
profile_page
)
def
test_make_profile_private
(
self
):
"""
Scenario: Verify that the user can change their privacy.
Given that I am a registered user
And I visit my public profile page
And I set the profile visibility to private
Then a user preference changed event should be recorded
When I reload the page
Then the profile visibility should be shown as private
"""
username
,
user_id
=
self
.
log_in_as_unique_user
()
profile_page
=
self
.
visit_profile_page
(
username
,
privacy
=
self
.
PRIVACY_PUBLIC
)
profile_page
.
privacy
=
self
.
PRIVACY_PRIVATE
self
.
verify_user_preference_changed_event
(
username
,
user_id
,
"account_privacy"
,
old_value
=
self
.
PRIVACY_PUBLIC
,
new_value
=
self
.
PRIVACY_PRIVATE
,
)
# Reload the page and verify that the profile is now private
self
.
browser
.
refresh
()
profile_page
.
wait_for_page
()
self
.
verify_profile_page_is_private
(
profile_page
)
def
test_dashboard_learner_profile_link
(
self
):
def
test_dashboard_learner_profile_link
(
self
):
"""
"""
Scenario: Verify that my profile link is present on dashboard page and we can navigate to correct page.
Scenario: Verify that my profile link is present on dashboard page and we can navigate to correct page.
...
@@ -177,10 +266,7 @@ class OwnLearnerProfilePageTest(LearnerProfileTestMixin, WebAppTest):
...
@@ -177,10 +266,7 @@ class OwnLearnerProfilePageTest(LearnerProfileTestMixin, WebAppTest):
"""
"""
username
,
user_id
=
self
.
log_in_as_unique_user
()
username
,
user_id
=
self
.
log_in_as_unique_user
()
profile_page
=
self
.
visit_profile_page
(
username
,
privacy
=
self
.
PRIVACY_PRIVATE
)
profile_page
=
self
.
visit_profile_page
(
username
,
privacy
=
self
.
PRIVACY_PRIVATE
)
self
.
verify_profile_page_is_private
(
profile_page
)
self
.
assertTrue
(
profile_page
.
privacy_field_visible
)
self
.
assertEqual
(
profile_page
.
visible_fields
,
self
.
PRIVATE_PROFILE_FIELDS
)
self
.
verify_profile_page_view_event
(
username
,
user_id
,
visibility
=
self
.
PRIVACY_PRIVATE
)
self
.
verify_profile_page_view_event
(
username
,
user_id
,
visibility
=
self
.
PRIVACY_PRIVATE
)
def
test_fields_on_my_public_profile
(
self
):
def
test_fields_on_my_public_profile
(
self
):
...
@@ -197,12 +283,7 @@ class OwnLearnerProfilePageTest(LearnerProfileTestMixin, WebAppTest):
...
@@ -197,12 +283,7 @@ class OwnLearnerProfilePageTest(LearnerProfileTestMixin, WebAppTest):
"""
"""
username
,
user_id
=
self
.
log_in_as_unique_user
()
username
,
user_id
=
self
.
log_in_as_unique_user
()
profile_page
=
self
.
visit_profile_page
(
username
,
privacy
=
self
.
PRIVACY_PUBLIC
)
profile_page
=
self
.
visit_profile_page
(
username
,
privacy
=
self
.
PRIVACY_PUBLIC
)
self
.
verify_profile_page_is_public
(
profile_page
)
self
.
assertTrue
(
profile_page
.
privacy_field_visible
)
self
.
assertEqual
(
profile_page
.
visible_fields
,
self
.
PUBLIC_PROFILE_FIELDS
)
self
.
assertEqual
(
profile_page
.
editable_fields
,
self
.
PUBLIC_PROFILE_EDITABLE_FIELDS
)
self
.
verify_profile_page_view_event
(
username
,
user_id
,
visibility
=
self
.
PRIVACY_PUBLIC
)
self
.
verify_profile_page_view_event
(
username
,
user_id
,
visibility
=
self
.
PRIVACY_PUBLIC
)
def
_test_dropdown_field
(
self
,
profile_page
,
field_id
,
new_value
,
displayed_value
,
mode
):
def
_test_dropdown_field
(
self
,
profile_page
,
field_id
,
new_value
,
displayed_value
,
mode
):
...
@@ -510,7 +591,7 @@ class OwnLearnerProfilePageTest(LearnerProfileTestMixin, WebAppTest):
...
@@ -510,7 +591,7 @@ class OwnLearnerProfilePageTest(LearnerProfileTestMixin, WebAppTest):
profile_page
.
visit
()
profile_page
.
visit
()
self
.
assertTrue
(
profile_page
.
profile_has_default_image
)
self
.
assertTrue
(
profile_page
.
profile_has_default_image
)
self
.
assert_event_emitted_num_times
(
user_id
,
'profile_image_uploaded_at'
,
1
)
self
.
assert_event_emitted_num_times
(
user_id
,
'profile_image_uploaded_at'
,
2
)
def
test_user_cannot_remove_default_image
(
self
):
def
test_user_cannot_remove_default_image
(
self
):
"""
"""
...
@@ -539,8 +620,10 @@ class OwnLearnerProfilePageTest(LearnerProfileTestMixin, WebAppTest):
...
@@ -539,8 +620,10 @@ class OwnLearnerProfilePageTest(LearnerProfileTestMixin, WebAppTest):
"""
"""
username
,
user_id
=
self
.
log_in_as_unique_user
()
username
,
user_id
=
self
.
log_in_as_unique_user
()
profile_page
=
self
.
visit_profile_page
(
username
,
privacy
=
self
.
PRIVACY_PUBLIC
)
profile_page
=
self
.
visit_profile_page
(
username
,
privacy
=
self
.
PRIVACY_PUBLIC
)
self
.
assert_default_image_has_public_access
(
profile_page
)
profile_page
.
upload_file
(
filename
=
'image.jpg'
)
profile_page
.
upload_file
(
filename
=
'image.jpg'
)
profile_page
.
upload_file
(
filename
=
'image.jpg'
)
self
.
assertTrue
(
profile_page
.
image_upload_success
)
profile_page
.
upload_file
(
filename
=
'image.jpg'
,
wait_for_upload_button
=
False
)
self
.
assert_event_emitted_num_times
(
user_id
,
'profile_image_uploaded_at'
,
2
)
self
.
assert_event_emitted_num_times
(
user_id
,
'profile_image_uploaded_at'
,
2
)
...
@@ -560,9 +643,7 @@ class DifferentUserLearnerProfilePageTest(LearnerProfileTestMixin, WebAppTest):
...
@@ -560,9 +643,7 @@ class DifferentUserLearnerProfilePageTest(LearnerProfileTestMixin, WebAppTest):
different_username
,
different_user_id
=
self
.
_initialize_different_user
(
privacy
=
self
.
PRIVACY_PRIVATE
)
different_username
,
different_user_id
=
self
.
_initialize_different_user
(
privacy
=
self
.
PRIVACY_PRIVATE
)
username
,
__
=
self
.
log_in_as_unique_user
()
username
,
__
=
self
.
log_in_as_unique_user
()
profile_page
=
self
.
visit_profile_page
(
different_username
)
profile_page
=
self
.
visit_profile_page
(
different_username
)
self
.
verify_profile_page_is_private
(
profile_page
,
is_editable
=
False
)
self
.
assertFalse
(
profile_page
.
privacy_field_visible
)
self
.
assertEqual
(
profile_page
.
visible_fields
,
self
.
PRIVATE_PROFILE_FIELDS
)
self
.
verify_profile_page_view_event
(
username
,
different_user_id
,
visibility
=
self
.
PRIVACY_PRIVATE
)
self
.
verify_profile_page_view_event
(
username
,
different_user_id
,
visibility
=
self
.
PRIVACY_PRIVATE
)
def
test_different_user_under_age
(
self
):
def
test_different_user_under_age
(
self
):
...
@@ -601,9 +682,7 @@ class DifferentUserLearnerProfilePageTest(LearnerProfileTestMixin, WebAppTest):
...
@@ -601,9 +682,7 @@ class DifferentUserLearnerProfilePageTest(LearnerProfileTestMixin, WebAppTest):
username
,
__
=
self
.
log_in_as_unique_user
()
username
,
__
=
self
.
log_in_as_unique_user
()
profile_page
=
self
.
visit_profile_page
(
different_username
)
profile_page
=
self
.
visit_profile_page
(
different_username
)
profile_page
.
wait_for_public_fields
()
profile_page
.
wait_for_public_fields
()
self
.
assertFalse
(
profile_page
.
privacy_field_visible
)
self
.
verify_profile_page_is_public
(
profile_page
,
is_editable
=
False
)
self
.
assertEqual
(
profile_page
.
visible_fields
,
self
.
PUBLIC_PROFILE_FIELDS
)
self
.
assertEqual
(
profile_page
.
editable_fields
,
[])
self
.
verify_profile_page_view_event
(
username
,
different_user_id
,
visibility
=
self
.
PRIVACY_PUBLIC
)
self
.
verify_profile_page_view_event
(
username
,
different_user_id
,
visibility
=
self
.
PRIVACY_PUBLIC
)
def
_initialize_different_user
(
self
,
privacy
=
None
,
birth_year
=
None
):
def
_initialize_different_user
(
self
,
privacy
=
None
,
birth_year
=
None
):
...
...
openedx/core/djangoapps/user_api/accounts/api.py
View file @
7742735f
...
@@ -8,6 +8,7 @@ from django.core.validators import validate_email, validate_slug, ValidationErro
...
@@ -8,6 +8,7 @@ from django.core.validators import validate_email, validate_slug, ValidationErro
from
student.models
import
User
,
UserProfile
,
Registration
,
USER_SETTINGS_CHANGED_EVENT_NAME
from
student.models
import
User
,
UserProfile
,
Registration
,
USER_SETTINGS_CHANGED_EVENT_NAME
from
student
import
views
as
student_views
from
student
import
views
as
student_views
from
util.model_utils
import
emit_setting_changed_event
from
..errors
import
(
from
..errors
import
(
AccountUpdateError
,
AccountValidationError
,
AccountUsernameInvalid
,
AccountPasswordInvalid
,
AccountUpdateError
,
AccountValidationError
,
AccountUsernameInvalid
,
AccountPasswordInvalid
,
...
@@ -24,7 +25,6 @@ from . import (
...
@@ -24,7 +25,6 @@ from . import (
USERNAME_MIN_LENGTH
,
USERNAME_MAX_LENGTH
USERNAME_MIN_LENGTH
,
USERNAME_MAX_LENGTH
)
)
from
.serializers
import
AccountLegacyProfileSerializer
,
AccountUserSerializer
from
.serializers
import
AccountLegacyProfileSerializer
,
AccountUserSerializer
from
eventtracking
import
tracker
@intercept_errors
(
UserAPIInternalError
,
ignore_errors
=
[
UserAPIRequestError
])
@intercept_errors
(
UserAPIInternalError
,
ignore_errors
=
[
UserAPIRequestError
])
...
@@ -199,15 +199,13 @@ def update_account_settings(requesting_user, update, username=None):
...
@@ -199,15 +199,13 @@ def update_account_settings(requesting_user, update, username=None):
if
"language_proficiencies"
in
update
:
if
"language_proficiencies"
in
update
:
new_language_proficiencies
=
legacy_profile_serializer
.
data
[
"language_proficiencies"
]
new_language_proficiencies
=
legacy_profile_serializer
.
data
[
"language_proficiencies"
]
tracker
.
emit
(
emit_setting_changed_event
(
USER_SETTINGS_CHANGED_EVENT_NAME
,
user
=
existing_user
,
{
event_name
=
USER_SETTINGS_CHANGED_EVENT_NAME
,
"setting"
:
"language_proficiencies"
,
db_table
=
existing_user_profile
.
language_proficiencies
.
model
.
_meta
.
db_table
,
"old"
:
old_language_proficiencies
,
setting_name
=
"language_proficiencies"
,
"new"
:
new_language_proficiencies
,
old_value
=
old_language_proficiencies
,
"user_id"
:
existing_user
.
id
,
new_value
=
new_language_proficiencies
,
"table"
:
existing_user_profile
.
language_proficiencies
.
model
.
_meta
.
db_table
,
}
)
)
# If the name was changed, store information about the change operation. This is outside of the
# If the name was changed, store information about the change operation. This is outside of the
...
...
openedx/core/djangoapps/user_api/models.py
View file @
7742735f
from
django.contrib.auth.models
import
User
from
django.contrib.auth.models
import
User
from
django.core.validators
import
RegexValidator
from
django.core.validators
import
RegexValidator
from
django.db
import
models
from
django.db
import
models
from
django.db.models.signals
import
pre_delete
,
post_delete
,
pre_save
,
post_save
from
django.dispatch
import
receiver
from
model_utils.models
import
TimeStampedModel
from
model_utils.models
import
TimeStampedModel
from
student.models
import
USER_SETTINGS_CHANGED_EVENT_NAME
from
util.model_utils
import
get_changed_fields_dict
,
emit_setting_changed_event
from
xmodule_django.models
import
CourseKeyField
from
xmodule_django.models
import
CourseKeyField
# Currently, the "student" app is responsible for
# Currently, the "student" app is responsible for
...
@@ -47,6 +51,39 @@ class UserPreference(models.Model):
...
@@ -47,6 +51,39 @@ class UserPreference(models.Model):
return
None
return
None
@receiver
(
pre_save
,
sender
=
UserPreference
)
def
pre_save_callback
(
sender
,
**
kwargs
):
"""
Event changes to user preferences.
"""
user_preference
=
kwargs
[
"instance"
]
user_preference
.
_old_value
=
get_changed_fields_dict
(
user_preference
,
sender
)
.
get
(
"value"
,
None
)
@receiver
(
post_save
,
sender
=
UserPreference
)
def
post_save_callback
(
sender
,
**
kwargs
):
"""
Event changes to user preferences.
"""
user_preference
=
kwargs
[
"instance"
]
emit_setting_changed_event
(
user_preference
.
user
,
USER_SETTINGS_CHANGED_EVENT_NAME
,
sender
.
_meta
.
db_table
,
user_preference
.
key
,
user_preference
.
_old_value
,
user_preference
.
value
)
user_preference
.
_old_value
=
None
@receiver
(
post_delete
,
sender
=
UserPreference
)
def
post_delete_callback
(
sender
,
**
kwargs
):
"""
Event changes to user preferences.
"""
user_preference
=
kwargs
[
"instance"
]
emit_setting_changed_event
(
user_preference
.
user
,
USER_SETTINGS_CHANGED_EVENT_NAME
,
sender
.
_meta
.
db_table
,
user_preference
.
key
,
user_preference
.
value
,
None
)
class
UserCourseTag
(
models
.
Model
):
class
UserCourseTag
(
models
.
Model
):
"""
"""
Per-course user tags, to be used by various things that want to store tags about
Per-course user tags, to be used by various things that want to store tags about
...
...
openedx/core/djangoapps/user_api/tests/test_models.py
View file @
7742735f
import
json
from
django.db
import
IntegrityError
from
django.db
import
IntegrityError
from
django.test
import
TestCase
from
student.models
import
USER_SETTINGS_CHANGED_EVENT_NAME
from
student.tests.factories
import
UserFactory
from
util.testing
import
EventTestMixin
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
student.tests.factories
import
UserFactory
from
..tests.factories
import
UserPreferenceFactory
,
UserCourseTagFactory
,
UserOrgTagFactory
from
..tests.factories
import
UserPreferenceFactory
,
UserCourseTagFactory
,
UserOrgTagFactory
from
..models
import
UserPreference
from
..models
import
UserPreference
from
..preferences.api
import
set_user_preference
from
..preferences.api
import
set_user_preference
USER_PREFERENCE_TABLE_NAME
=
"user_api_userpreference"
class
UserPreferenceModelTest
(
ModuleStoreTestCase
):
class
UserPreferenceModelTest
(
ModuleStoreTestCase
):
def
test_duplicate_user_key
(
self
):
def
test_duplicate_user_key
(
self
):
...
@@ -83,3 +91,84 @@ class UserPreferenceModelTest(ModuleStoreTestCase):
...
@@ -83,3 +91,84 @@ class UserPreferenceModelTest(ModuleStoreTestCase):
# get preference for key that doesn't exist for user
# get preference for key that doesn't exist for user
pref
=
UserPreference
.
get_value
(
user
,
'testkey_none'
)
pref
=
UserPreference
.
get_value
(
user
,
'testkey_none'
)
self
.
assertIsNone
(
pref
)
self
.
assertIsNone
(
pref
)
class
TestUserPreferenceEvents
(
EventTestMixin
,
TestCase
):
"""
Mixin for verifying that user preference events are fired correctly.
"""
def
setUp
(
self
):
super
(
TestUserPreferenceEvents
,
self
)
.
setUp
(
'util.model_utils.tracker'
)
self
.
user
=
UserFactory
.
create
()
self
.
TEST_KEY
=
"test key"
self
.
TEST_VALUE
=
"test value"
self
.
user_preference
=
UserPreference
.
objects
.
create
(
user
=
self
.
user
,
key
=
self
.
TEST_KEY
,
value
=
self
.
TEST_VALUE
)
self
.
reset_tracker
()
def
test_create_user_preference
(
self
):
"""
Verify that we emit an event when a user preference is created.
"""
UserPreference
.
objects
.
create
(
user
=
self
.
user
,
key
=
"new key"
,
value
=
"new value"
)
self
.
assert_user_preference_event_emitted
(
key
=
"new key"
,
old
=
None
,
new
=
"new value"
)
def
test_update_user_preference
(
self
):
"""
Verify that we emit an event when a user preference is updated.
"""
self
.
user_preference
.
value
=
"new value"
self
.
user_preference
.
save
()
self
.
assert_user_preference_event_emitted
(
key
=
self
.
TEST_KEY
,
old
=
self
.
TEST_VALUE
,
new
=
"new value"
)
def
test_delete_user_preference
(
self
):
"""
Verify that we emit an event when a user preference is deleted.
"""
self
.
user_preference
.
delete
()
self
.
assert_user_preference_event_emitted
(
key
=
self
.
TEST_KEY
,
old
=
self
.
TEST_VALUE
,
new
=
None
)
def
assert_user_preference_event_emitted
(
self
,
key
,
old
,
new
,
truncated
=
[]):
"""
Helper method to assert that we emit the expected user preference events.
Expected settings are passed in via `kwargs`.
"""
self
.
assert_event_emitted
(
USER_SETTINGS_CHANGED_EVENT_NAME
,
table
=
USER_PREFERENCE_TABLE_NAME
,
user_id
=
self
.
user
.
id
,
setting
=
key
,
old
=
old
,
new
=
new
,
truncated
=
truncated
,
)
def
test_truncated_user_preference_event
(
self
):
"""
Verify that we truncate the preference value if it is too long.
"""
MAX_STRING_LENGTH
=
12500
OVERSIZE_STRING_LENGTH
=
MAX_STRING_LENGTH
+
10
self
.
user_preference
.
value
=
"z"
*
OVERSIZE_STRING_LENGTH
self
.
user_preference
.
save
()
self
.
assert_user_preference_event_emitted
(
key
=
self
.
TEST_KEY
,
old
=
self
.
TEST_VALUE
,
new
=
"z"
*
MAX_STRING_LENGTH
,
truncated
=
[
"new"
],
)
self
.
user_preference
.
value
=
"x"
*
OVERSIZE_STRING_LENGTH
self
.
user_preference
.
save
()
self
.
assert_user_preference_event_emitted
(
key
=
self
.
TEST_KEY
,
old
=
"z"
*
MAX_STRING_LENGTH
,
new
=
"x"
*
MAX_STRING_LENGTH
,
truncated
=
[
"old"
,
"new"
],
)
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