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
b85ea12a
Commit
b85ea12a
authored
Oct 01, 2015
by
Cliff Dyer
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #9958 from edx/cdyer/MA-1280-restful-conventions
Make the profile_image api more restful.
parents
d1f4d3a6
e200015c
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
231 additions
and
124 deletions
+231
-124
openedx/core/djangoapps/profile_images/tests/test_views.py
+114
-54
openedx/core/djangoapps/profile_images/urls.py
+6
-1
openedx/core/djangoapps/profile_images/views.py
+100
-64
openedx/core/djangoapps/user_api/urls.py
+11
-5
No files found.
openedx/core/djangoapps/profile_images/tests/test_views.py
View file @
b85ea12a
...
@@ -8,6 +8,8 @@ import unittest
...
@@ -8,6 +8,8 @@ import unittest
from
django.conf
import
settings
from
django.conf
import
settings
from
django.core.urlresolvers
import
reverse
from
django.core.urlresolvers
import
reverse
from
django.http
import
HttpResponse
import
mock
import
mock
from
mock
import
patch
from
mock
import
patch
from
PIL
import
Image
from
PIL
import
Image
...
@@ -64,7 +66,7 @@ class PatchedClient(APIClient):
...
@@ -64,7 +66,7 @@ class PatchedClient(APIClient):
return
super
(
PatchedClient
,
self
)
.
request
(
*
args
,
**
kwargs
)
return
super
(
PatchedClient
,
self
)
.
request
(
*
args
,
**
kwargs
)
class
ProfileImageEndpoint
TestCase
(
UserSettingsEventTestMixin
,
APITestCase
):
class
ProfileImageEndpoint
Mixin
(
UserSettingsEventTestMixin
):
"""
"""
Base class / shared infrastructure for tests of profile_image "upload" and
Base class / shared infrastructure for tests of profile_image "upload" and
"remove" endpoints.
"remove" endpoints.
...
@@ -72,9 +74,10 @@ class ProfileImageEndpointTestCase(UserSettingsEventTestMixin, APITestCase):
...
@@ -72,9 +74,10 @@ class ProfileImageEndpointTestCase(UserSettingsEventTestMixin, APITestCase):
# subclasses should override this with the name of the view under test, as
# subclasses should override this with the name of the view under test, as
# per the urls.py configuration.
# per the urls.py configuration.
_view_name
=
None
_view_name
=
None
client_class
=
PatchedClient
def
setUp
(
self
):
def
setUp
(
self
):
super
(
ProfileImageEndpoint
TestCase
,
self
)
.
setUp
()
super
(
ProfileImageEndpoint
Mixin
,
self
)
.
setUp
()
self
.
user
=
UserFactory
.
create
(
password
=
TEST_PASSWORD
)
self
.
user
=
UserFactory
.
create
(
password
=
TEST_PASSWORD
)
# Ensure that parental controls don't apply to this user
# Ensure that parental controls don't apply to this user
self
.
user
.
profile
.
year_of_birth
=
1980
self
.
user
.
profile
.
year_of_birth
=
1980
...
@@ -92,7 +95,7 @@ class ProfileImageEndpointTestCase(UserSettingsEventTestMixin, APITestCase):
...
@@ -92,7 +95,7 @@ class ProfileImageEndpointTestCase(UserSettingsEventTestMixin, APITestCase):
self
.
reset_tracker
()
self
.
reset_tracker
()
def
tearDown
(
self
):
def
tearDown
(
self
):
super
(
ProfileImageEndpoint
TestCase
,
self
)
.
tearDown
()
super
(
ProfileImageEndpoint
Mixin
,
self
)
.
tearDown
()
for
name
in
get_profile_image_names
(
self
.
user
.
username
)
.
values
():
for
name
in
get_profile_image_names
(
self
.
user
.
username
)
.
values
():
self
.
storage
.
delete
(
name
)
self
.
storage
.
delete
(
name
)
...
@@ -136,18 +139,46 @@ class ProfileImageEndpointTestCase(UserSettingsEventTestMixin, APITestCase):
...
@@ -136,18 +139,46 @@ class ProfileImageEndpointTestCase(UserSettingsEventTestMixin, APITestCase):
profile
=
self
.
user
.
profile
.
__class__
.
objects
.
get
(
user
=
self
.
user
)
profile
=
self
.
user
.
profile
.
__class__
.
objects
.
get
(
user
=
self
.
user
)
self
.
assertEqual
(
profile
.
has_profile_image
,
has_profile_image
)
self
.
assertEqual
(
profile
.
has_profile_image
,
has_profile_image
)
def
check_anonymous_request_rejected
(
self
,
method
):
"""
Make sure that the specified method rejects access by unauthorized users.
"""
anonymous_client
=
APIClient
()
request_method
=
getattr
(
anonymous_client
,
method
)
response
=
request_method
(
self
.
url
)
self
.
check_response
(
response
,
401
)
self
.
assert_no_events_were_emitted
()
@unittest.skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Profile Image API is only supported in LMS'
)
@mock.patch
(
'openedx.core.djangoapps.profile_images.views.log'
)
class
ProfileImageViewGeneralTestCase
(
ProfileImageEndpointMixin
,
APITestCase
):
"""
Tests for the profile image endpoint
"""
_view_name
=
"accounts_profile_image_api"
def
test_unsupported_methods
(
self
,
mock_log
):
"""
Test that GET, PUT, and PATCH are not supported.
"""
self
.
assertEqual
(
405
,
self
.
client
.
get
(
self
.
url
)
.
status_code
)
self
.
assertEqual
(
405
,
self
.
client
.
put
(
self
.
url
)
.
status_code
)
self
.
assertEqual
(
405
,
self
.
client
.
patch
(
self
.
url
)
.
status_code
)
# pylint: disable=no-member
self
.
assertFalse
(
mock_log
.
info
.
called
)
self
.
assert_no_events_were_emitted
()
@unittest.skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Profile Image API is only supported in LMS'
)
@unittest.skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Profile Image API is only supported in LMS'
)
@mock.patch
(
'openedx.core.djangoapps.profile_images.views.log'
)
@mock.patch
(
'openedx.core.djangoapps.profile_images.views.log'
)
class
ProfileImage
UploadTestCase
(
ProfileImageEndpoint
TestCase
):
class
ProfileImage
ViewPostTestCase
(
ProfileImageEndpointMixin
,
API
TestCase
):
"""
"""
Tests for the
profile_image upload
endpoint.
Tests for the
POST method of the profile_image api
endpoint.
"""
"""
_view_name
=
"
profile_image_upload
"
_view_name
=
"
accounts_profile_image_api
"
# Use the patched version of the API client to workaround a unicode issue
# Use the patched version of the API client to workaround a unicode issue
# with DRF 3.1 and Django 1.4. Remove this after we upgrade Django past 1.4!
# with DRF 3.1 and Django 1.4. Remove this after we upgrade Django past 1.4!
client_class
=
PatchedClient
def
check_upload_event_emitted
(
self
,
old
=
None
,
new
=
TEST_UPLOAD_DT
):
def
check_upload_event_emitted
(
self
,
old
=
None
,
new
=
TEST_UPLOAD_DT
):
"""
"""
...
@@ -158,26 +189,12 @@ class ProfileImageUploadTestCase(ProfileImageEndpointTestCase):
...
@@ -158,26 +189,12 @@ class ProfileImageUploadTestCase(ProfileImageEndpointTestCase):
setting
=
'profile_image_uploaded_at'
,
old
=
old
,
new
=
new
setting
=
'profile_image_uploaded_at'
,
old
=
old
,
new
=
new
)
)
def
test_unsupported_methods
(
self
,
mock_log
):
"""
Test that GET, PUT, PATCH, and DELETE are not supported.
"""
self
.
assertEqual
(
405
,
self
.
client
.
get
(
self
.
url
)
.
status_code
)
self
.
assertEqual
(
405
,
self
.
client
.
put
(
self
.
url
)
.
status_code
)
self
.
assertEqual
(
405
,
self
.
client
.
patch
(
self
.
url
)
.
status_code
)
self
.
assertEqual
(
405
,
self
.
client
.
delete
(
self
.
url
)
.
status_code
)
self
.
assertFalse
(
mock_log
.
info
.
called
)
self
.
assert_no_events_were_emitted
()
def
test_anonymous_access
(
self
,
mock_log
):
def
test_anonymous_access
(
self
,
mock_log
):
"""
"""
Test that an anonymous client (not logged in) cannot POST.
Test that an anonymous client (not logged in) cannot
call
POST.
"""
"""
anonymous_client
=
APIClient
()
self
.
check_anonymous_request_rejected
(
'post'
)
response
=
anonymous_client
.
post
(
self
.
url
)
self
.
assertEqual
(
401
,
response
.
status_code
)
self
.
assertFalse
(
mock_log
.
info
.
called
)
self
.
assertFalse
(
mock_log
.
info
.
called
)
self
.
assert_no_events_were_emitted
()
@patch
(
'openedx.core.djangoapps.profile_images.views._make_upload_dt'
,
side_effect
=
[
TEST_UPLOAD_DT
,
TEST_UPLOAD_DT2
])
@patch
(
'openedx.core.djangoapps.profile_images.views._make_upload_dt'
,
side_effect
=
[
TEST_UPLOAD_DT
,
TEST_UPLOAD_DT2
])
def
test_upload_self
(
self
,
mock_make_image_version
,
mock_log
):
# pylint: disable=unused-argument
def
test_upload_self
(
self
,
mock_make_image_version
,
mock_log
):
# pylint: disable=unused-argument
...
@@ -204,7 +221,8 @@ class ProfileImageUploadTestCase(ProfileImageEndpointTestCase):
...
@@ -204,7 +221,8 @@ class ProfileImageUploadTestCase(ProfileImageEndpointTestCase):
def
test_upload_other
(
self
,
mock_log
):
def
test_upload_other
(
self
,
mock_log
):
"""
"""
Test that an authenticated user cannot POST to another user's upload endpoint.
Test that an authenticated user cannot POST to another user's upload
endpoint.
"""
"""
different_user
=
UserFactory
.
create
(
password
=
TEST_PASSWORD
)
different_user
=
UserFactory
.
create
(
password
=
TEST_PASSWORD
)
# Ignore UserProfileFactory creation events.
# Ignore UserProfileFactory creation events.
...
@@ -221,7 +239,8 @@ class ProfileImageUploadTestCase(ProfileImageEndpointTestCase):
...
@@ -221,7 +239,8 @@ class ProfileImageUploadTestCase(ProfileImageEndpointTestCase):
def
test_upload_staff
(
self
,
mock_log
):
def
test_upload_staff
(
self
,
mock_log
):
"""
"""
Test that an authenticated staff cannot POST to another user's upload endpoint.
Test that an authenticated staff cannot POST to another user's upload
endpoint.
"""
"""
staff_user
=
UserFactory
(
is_staff
=
True
,
password
=
TEST_PASSWORD
)
staff_user
=
UserFactory
(
is_staff
=
True
,
password
=
TEST_PASSWORD
)
# Ignore UserProfileFactory creation events.
# Ignore UserProfileFactory creation events.
...
@@ -306,14 +325,14 @@ class ProfileImageUploadTestCase(ProfileImageEndpointTestCase):
...
@@ -306,14 +325,14 @@ class ProfileImageUploadTestCase(ProfileImageEndpointTestCase):
@unittest.skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Profile Image API is only supported in LMS'
)
@unittest.skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Profile Image API is only supported in LMS'
)
@mock.patch
(
'openedx.core.djangoapps.profile_images.views.log'
)
@mock.patch
(
'openedx.core.djangoapps.profile_images.views.log'
)
class
ProfileImage
RemoveTestCase
(
ProfileImageEndpoint
TestCase
):
class
ProfileImage
ViewDeleteTestCase
(
ProfileImageEndpointMixin
,
API
TestCase
):
"""
"""
Tests for the
profile_image remov
e endpoint.
Tests for the
DELETE method of the profile_imag
e endpoint.
"""
"""
_view_name
=
"
profile_image_remove
"
_view_name
=
"
accounts_profile_image_api
"
def
setUp
(
self
):
def
setUp
(
self
):
super
(
ProfileImage
Remov
eTestCase
,
self
)
.
setUp
()
super
(
ProfileImage
ViewDelet
eTestCase
,
self
)
.
setUp
()
with
make_image_file
()
as
image_file
:
with
make_image_file
()
as
image_file
:
create_profile_images
(
image_file
,
get_profile_image_names
(
self
.
user
.
username
))
create_profile_images
(
image_file
,
get_profile_image_names
(
self
.
user
.
username
))
self
.
check_images
()
self
.
check_images
()
...
@@ -330,34 +349,19 @@ class ProfileImageRemoveTestCase(ProfileImageEndpointTestCase):
...
@@ -330,34 +349,19 @@ class ProfileImageRemoveTestCase(ProfileImageEndpointTestCase):
setting
=
'profile_image_uploaded_at'
,
old
=
TEST_UPLOAD_DT
,
new
=
None
setting
=
'profile_image_uploaded_at'
,
old
=
TEST_UPLOAD_DT
,
new
=
None
)
)
def
test_unsupported_methods
(
self
,
mock_log
):
"""
Test that GET, PUT, PATCH, and DELETE are not supported.
"""
self
.
assertEqual
(
405
,
self
.
client
.
get
(
self
.
url
)
.
status_code
)
self
.
assertEqual
(
405
,
self
.
client
.
put
(
self
.
url
)
.
status_code
)
self
.
assertEqual
(
405
,
self
.
client
.
patch
(
self
.
url
)
.
status_code
)
self
.
assertEqual
(
405
,
self
.
client
.
delete
(
self
.
url
)
.
status_code
)
self
.
assertFalse
(
mock_log
.
info
.
called
)
self
.
assert_no_events_were_emitted
()
def
test_anonymous_access
(
self
,
mock_log
):
def
test_anonymous_access
(
self
,
mock_log
):
"""
"""
Test that an anonymous client (not logged in) cannot call
GET or POST
.
Test that an anonymous client (not logged in) cannot call
DELETE
.
"""
"""
anonymous_client
=
APIClient
()
self
.
check_anonymous_request_rejected
(
'delete'
)
for
request
in
(
anonymous_client
.
get
,
anonymous_client
.
post
):
response
=
request
(
self
.
url
)
self
.
assertEqual
(
401
,
response
.
status_code
)
self
.
assertFalse
(
mock_log
.
info
.
called
)
self
.
assertFalse
(
mock_log
.
info
.
called
)
self
.
assert_no_events_were_emitted
()
def
test_remove_self
(
self
,
mock_log
):
def
test_remove_self
(
self
,
mock_log
):
"""
"""
Test that an authenticated user can
POST
to remove their own profile
Test that an authenticated user can
DELETE
to remove their own profile
images.
images.
"""
"""
response
=
self
.
client
.
post
(
self
.
url
)
response
=
self
.
client
.
delete
(
self
.
url
)
self
.
check_response
(
response
,
204
)
self
.
check_response
(
response
,
204
)
self
.
check_images
(
False
)
self
.
check_images
(
False
)
self
.
check_has_profile_image
(
False
)
self
.
check_has_profile_image
(
False
)
...
@@ -369,7 +373,7 @@ class ProfileImageRemoveTestCase(ProfileImageEndpointTestCase):
...
@@ -369,7 +373,7 @@ class ProfileImageRemoveTestCase(ProfileImageEndpointTestCase):
def
test_remove_other
(
self
,
mock_log
):
def
test_remove_other
(
self
,
mock_log
):
"""
"""
Test that an authenticated user cannot
POST
to remove another user's
Test that an authenticated user cannot
DELETE
to remove another user's
profile images.
profile images.
"""
"""
different_user
=
UserFactory
.
create
(
password
=
TEST_PASSWORD
)
different_user
=
UserFactory
.
create
(
password
=
TEST_PASSWORD
)
...
@@ -377,7 +381,7 @@ class ProfileImageRemoveTestCase(ProfileImageEndpointTestCase):
...
@@ -377,7 +381,7 @@ class ProfileImageRemoveTestCase(ProfileImageEndpointTestCase):
self
.
reset_tracker
()
self
.
reset_tracker
()
different_client
=
APIClient
()
different_client
=
APIClient
()
different_client
.
login
(
username
=
different_user
.
username
,
password
=
TEST_PASSWORD
)
different_client
.
login
(
username
=
different_user
.
username
,
password
=
TEST_PASSWORD
)
response
=
different_client
.
post
(
self
.
url
)
response
=
different_client
.
delete
(
self
.
url
)
self
.
check_response
(
response
,
404
)
self
.
check_response
(
response
,
404
)
self
.
check_images
(
True
)
# thumbnails should remain intact.
self
.
check_images
(
True
)
# thumbnails should remain intact.
self
.
check_has_profile_image
(
True
)
self
.
check_has_profile_image
(
True
)
...
@@ -386,13 +390,13 @@ class ProfileImageRemoveTestCase(ProfileImageEndpointTestCase):
...
@@ -386,13 +390,13 @@ class ProfileImageRemoveTestCase(ProfileImageEndpointTestCase):
def
test_remove_staff
(
self
,
mock_log
):
def
test_remove_staff
(
self
,
mock_log
):
"""
"""
Test that an authenticated staff user can
POST
to remove another user's
Test that an authenticated staff user can
DELETE
to remove another user's
profile images.
profile images.
"""
"""
staff_user
=
UserFactory
(
is_staff
=
True
,
password
=
TEST_PASSWORD
)
staff_user
=
UserFactory
(
is_staff
=
True
,
password
=
TEST_PASSWORD
)
staff_client
=
APIClient
()
staff_client
=
APIClient
()
staff_client
.
login
(
username
=
staff_user
.
username
,
password
=
TEST_PASSWORD
)
staff_client
.
login
(
username
=
staff_user
.
username
,
password
=
TEST_PASSWORD
)
response
=
self
.
client
.
post
(
self
.
url
)
response
=
self
.
client
.
delete
(
self
.
url
)
self
.
check_response
(
response
,
204
)
self
.
check_response
(
response
,
204
)
self
.
check_images
(
False
)
self
.
check_images
(
False
)
self
.
check_has_profile_image
(
False
)
self
.
check_has_profile_image
(
False
)
...
@@ -405,13 +409,69 @@ class ProfileImageRemoveTestCase(ProfileImageEndpointTestCase):
...
@@ -405,13 +409,69 @@ class ProfileImageRemoveTestCase(ProfileImageEndpointTestCase):
@patch
(
'student.models.UserProfile.save'
)
@patch
(
'student.models.UserProfile.save'
)
def
test_remove_failure
(
self
,
user_profile_save
,
mock_log
):
def
test_remove_failure
(
self
,
user_profile_save
,
mock_log
):
"""
"""
Test that when
upload
validation fails, the proper HTTP response and
Test that when
remove
validation fails, the proper HTTP response and
messages are returned.
messages are returned.
"""
"""
user_profile_save
.
side_effect
=
[
Exception
(
u"whoops"
),
None
]
user_profile_save
.
side_effect
=
[
Exception
(
u"whoops"
),
None
]
with
self
.
assertRaises
(
Exception
):
with
self
.
assertRaises
(
Exception
):
self
.
client
.
post
(
self
.
url
)
self
.
client
.
delete
(
self
.
url
)
self
.
check_images
(
True
)
# thumbnails should remain intact.
self
.
check_images
(
True
)
# thumbnails should remain intact.
self
.
check_has_profile_image
(
True
)
self
.
check_has_profile_image
(
True
)
self
.
assertFalse
(
mock_log
.
info
.
called
)
self
.
assertFalse
(
mock_log
.
info
.
called
)
self
.
assert_no_events_were_emitted
()
self
.
assert_no_events_were_emitted
()
class
DeprecatedProfileImageTestMixin
(
ProfileImageEndpointMixin
):
"""
Actual tests for DeprecatedProfileImage.*TestCase classes defined here.
Requires:
self._view_name
self._replacement_method
"""
def
test_unsupported_methods
(
self
,
mock_log
):
"""
Test that GET, PUT, PATCH, and DELETE are not supported.
"""
self
.
assertEqual
(
405
,
self
.
client
.
get
(
self
.
url
)
.
status_code
)
self
.
assertEqual
(
405
,
self
.
client
.
put
(
self
.
url
)
.
status_code
)
self
.
assertEqual
(
405
,
self
.
client
.
patch
(
self
.
url
)
.
status_code
)
self
.
assertEqual
(
405
,
self
.
client
.
delete
(
self
.
url
)
.
status_code
)
self
.
assertFalse
(
mock_log
.
info
.
called
)
self
.
assert_no_events_were_emitted
()
def
test_post_calls_replacement_view_method
(
self
,
mock_log
):
"""
Test that calls to this view pass through the the new view.
"""
with
patch
(
self
.
_replacement_method
)
as
mock_method
:
mock_method
.
return_value
=
HttpResponse
()
self
.
client
.
post
(
self
.
url
)
assert
mock_method
.
called
self
.
assertFalse
(
mock_log
.
info
.
called
)
self
.
assert_no_events_were_emitted
()
@unittest.skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Profile Image API is only supported in LMS'
)
@mock.patch
(
'openedx.core.djangoapps.profile_images.views.log'
)
class
DeprecatedProfileImageUploadTestCase
(
DeprecatedProfileImageTestMixin
,
APITestCase
):
"""
Tests for the deprecated profile_image upload endpoint.
Actual tests defined on DeprecatedProfileImageTestMixin
"""
_view_name
=
'profile_image_upload'
_replacement_method
=
'openedx.core.djangoapps.profile_images.views.ProfileImageView.post'
@unittest.skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Profile Image API is only supported in LMS'
)
@mock.patch
(
'openedx.core.djangoapps.profile_images.views.log'
)
class
DeprecatedProfileImageRemoveTestCase
(
DeprecatedProfileImageTestMixin
,
APITestCase
):
"""
Tests for the deprecated profile_image remove endpoint.
Actual tests defined on DeprecatedProfileImageTestMixin
"""
_view_name
=
"profile_image_remove"
_replacement_method
=
'openedx.core.djangoapps.profile_images.views.ProfileImageView.delete'
openedx/core/djangoapps/profile_images/urls.py
View file @
b85ea12a
"""
"""
Defines the URL routes for this app.
Defines the URL routes for this app.
NOTE: These views are deprecated. These routes are superseded by
``/api/user/v1/accounts/{username}/image``, found in
``openedx.core.djangoapps.user_api.urls``.
"""
"""
from
.views
import
ProfileImageUploadView
,
ProfileImageRemoveView
from
django.conf.urls
import
patterns
,
url
from
django.conf.urls
import
patterns
,
url
from
.views
import
ProfileImageUploadView
,
ProfileImageRemoveView
USERNAME_PATTERN
=
r'(?P<username>[\w.+-]+)'
USERNAME_PATTERN
=
r'(?P<username>[\w.+-]+)'
urlpatterns
=
patterns
(
urlpatterns
=
patterns
(
...
...
openedx/core/djangoapps/profile_images/views.py
View file @
b85ea12a
...
@@ -35,49 +35,85 @@ def _make_upload_dt():
...
@@ -35,49 +35,85 @@ def _make_upload_dt():
return
datetime
.
datetime
.
utcnow
()
.
replace
(
tzinfo
=
utc
)
return
datetime
.
datetime
.
utcnow
()
.
replace
(
tzinfo
=
utc
)
class
ProfileImage
Upload
View
(
APIView
):
class
ProfileImageView
(
APIView
):
"""
"""
**Use Case**
**Use Case
s
**
* Upload an image for the user's profile
.
Add or remove profile images associated with user accounts
.
The requesting user must be signed in. The signed in user can only
The requesting user must be signed in. Users can only add profile
upload his or her own profile image.
images to their own account. Users with staff access can remove
profile images for other user accounts. All other users can remove
only their own profile images.
**Example Request**
**Example Request
s
**
POST /api/profile_images/v1/{username}/upload
POST /api/user/v1/accounts/{username}/image
DELETE /api/user/v1/accounts/{username}/image
**Example POST Responses**
When the requesting user attempts to upload an image for their own
account, the request returns one of the following responses:
**Example Responses**
* If the upload could not be performed, the request returns an HTTP 400
"Bad Request" response with information about why the request failed.
When the requesting user tries to upload the image for a different user, the
* If the upload is successful, the request returns an HTTP 204 "No
request returns one of the following responses
.
Content" response with no additional content
.
* If the requesting user has staff access, the request returns an HTTP 403
If the requesting user tries to upload an image for a different
"Forbidden" response.
user, the request returns one of the following responses:
* If
the requesting user does not have staff access, the request returns
* If
no user matches the "username" parameter, the request returns an
an
HTTP 404 "Not Found" response.
HTTP 404 "Not Found" response.
* If no user matches the "username" parameter, the request returns an HTTP
* If the user whose profile image is being uploaded exists, but the
404 "Not Found" response.
requesting user does not have staff access, the request returns an
HTTP 404 "Not Found" response.
* If the
upload could not be performed, the request returns an HTTP 400 "Bad
* If the
specified user exists, and the requesting user has staff
Request" response with more information
.
access, the request returns an HTTP 403 "Forbidden" response
.
* If the upload is successful, the request returns an HTTP 204 "No Content"
**Example DELETE Responses**
response with no additional content.
When the requesting user attempts to remove the profile image for
their own account, the request returns one of the following
responses:
* If the image could not be removed, the request returns an HTTP 400
"Bad Request" response with information about why the request failed.
* If the request successfully removes the image, the request returns
an HTTP 204 "No Content" response with no additional content.
When the requesting user tries to remove the profile image for a
different user, the view will return one of the following responses:
* If the requesting user has staff access, and the "username" parameter
matches a user, the profile image for the specified user is deleted,
and the request returns an HTTP 204 "No Content" response with no
additional content.
* If the requesting user has staff access, but no user is matched by
the "username" parameter, the request returns an HTTP 404 "Not Found"
response.
* If the requesting user does not have staff access, the request
returns an HTTP 404 "Not Found" response, regardless of whether
the user exists or not.
"""
"""
parser_classes
=
(
MultiPartParser
,
FormParser
,)
parser_classes
=
(
MultiPartParser
,
FormParser
)
authentication_classes
=
(
OAuth2AuthenticationAllowInactiveUser
,
SessionAuthenticationAllowInactiveUser
)
authentication_classes
=
(
OAuth2AuthenticationAllowInactiveUser
,
SessionAuthenticationAllowInactiveUser
)
permission_classes
=
(
permissions
.
IsAuthenticated
,
IsUserInUrl
)
permission_classes
=
(
permissions
.
IsAuthenticated
,
IsUserInUrl
)
def
post
(
self
,
request
,
username
):
def
post
(
self
,
request
,
username
):
"""
"""
POST /api/
profile_images/v1/{username}/upload
POST /api/
user/v1/accounts/{username}/image
"""
"""
# validate request:
# validate request:
# verify that the user's
# verify that the user's
# ensure any file was sent
# ensure any file was sent
...
@@ -121,50 +157,11 @@ class ProfileImageUploadView(APIView):
...
@@ -121,50 +157,11 @@ class ProfileImageUploadView(APIView):
# send client response.
# send client response.
return
Response
(
status
=
status
.
HTTP_204_NO_CONTENT
)
return
Response
(
status
=
status
.
HTTP_204_NO_CONTENT
)
def
delete
(
self
,
request
,
username
):
# pylint: disable=unused-argument
class
ProfileImageRemoveView
(
APIView
):
"""
**Use Case**
* Remove all of the profile images associated with the user's account.
The requesting user must be signed in.
Users with staff access can remove profile images for other user
accounts.
Users without staff access can only remove their own profile images.
**Example Request**
POST /api/profile_images/v1/{username}/remove
**Example Responses**
When the requesting user tries to remove the profile image for a
different user, the request returns one of the following responses.
* If the user does not have staff access, the request returns an HTTP
404 "Not Found" response.
* If no user matches the "username" parameter, the request returns an
HTTP 404 "Not Found" response.
* If the image could not be removed, the request returns an HTTP 400
"Bad Request" response with more information.
* If the request successfully removes the image, the request returns
an HTTP 204 "No Content" response with no additional content.
"""
authentication_classes
=
(
OAuth2AuthenticationAllowInactiveUser
,
SessionAuthenticationAllowInactiveUser
)
permission_classes
=
(
permissions
.
IsAuthenticated
,
IsUserInUrlOrStaff
)
def
post
(
self
,
request
,
username
):
# pylint: disable=unused-argument
"""
"""
POST /api/profile_images/v1/{username}/remov
e
DELETE /api/user/v1/accounts/{username}/imag
e
"""
"""
try
:
try
:
# update the user account to reflect that the images were removed.
# update the user account to reflect that the images were removed.
set_has_profile_image
(
username
,
False
)
set_has_profile_image
(
username
,
False
)
...
@@ -182,3 +179,42 @@ class ProfileImageRemoveView(APIView):
...
@@ -182,3 +179,42 @@ class ProfileImageRemoveView(APIView):
# send client response.
# send client response.
return
Response
(
status
=
status
.
HTTP_204_NO_CONTENT
)
return
Response
(
status
=
status
.
HTTP_204_NO_CONTENT
)
class
ProfileImageUploadView
(
APIView
):
"""
**DEPRECATION WARNING**
/api/profile_images/v1/{username}/upload is deprecated.
All requests should now be sent to
/api/user/v1/accounts/{username}/image
"""
parser_classes
=
ProfileImageView
.
parser_classes
authentication_classes
=
ProfileImageView
.
authentication_classes
permission_classes
=
ProfileImageView
.
permission_classes
def
post
(
self
,
request
,
username
):
"""
POST /api/profile_images/v1/{username}/upload
"""
return
ProfileImageView
()
.
post
(
request
,
username
)
class
ProfileImageRemoveView
(
APIView
):
"""
**DEPRECATION WARNING**
/api/profile_images/v1/{username}/remove is deprecated.
This endpoint's POST is replaced by the DELETE method at
/api/user/v1/accounts/{username}/image.
"""
authentication_classes
=
ProfileImageView
.
authentication_classes
permission_classes
=
ProfileImageView
.
permission_classes
def
post
(
self
,
request
,
username
):
"""
POST /api/profile_images/v1/{username}/remove
"""
return
ProfileImageView
()
.
delete
(
request
,
username
)
openedx/core/djangoapps/user_api/urls.py
View file @
b85ea12a
...
@@ -2,27 +2,33 @@
...
@@ -2,27 +2,33 @@
Defines the URL routes for this app.
Defines the URL routes for this app.
"""
"""
from
django.conf.urls
import
patterns
,
url
from
..profile_images.views
import
ProfileImageView
from
.accounts.views
import
AccountView
from
.accounts.views
import
AccountView
from
.preferences.views
import
PreferencesView
,
PreferencesDetailView
from
.preferences.views
import
PreferencesView
,
PreferencesDetailView
from
django.conf.urls
import
patterns
,
url
USERNAME_PATTERN
=
r'(?P<username>[\w.+-]+)'
USERNAME_PATTERN
=
r'(?P<username>[\w.+-]+)'
urlpatterns
=
patterns
(
urlpatterns
=
patterns
(
''
,
''
,
url
(
url
(
r'^v1/accounts/
'
+
USERNAME_PATTERN
+
'$'
,
r'^v1/accounts/
{}$'
.
format
(
USERNAME_PATTERN
)
,
AccountView
.
as_view
(),
AccountView
.
as_view
(),
name
=
"accounts_api"
name
=
"accounts_api"
),
),
url
(
url
(
r'^v1/preferences/'
+
USERNAME_PATTERN
+
'$'
,
r'^v1/accounts/{}/image$'
.
format
(
USERNAME_PATTERN
),
ProfileImageView
.
as_view
(),
name
=
"accounts_profile_image_api"
),
url
(
r'^v1/preferences/{}$'
.
format
(
USERNAME_PATTERN
),
PreferencesView
.
as_view
(),
PreferencesView
.
as_view
(),
name
=
"preferences_api"
name
=
"preferences_api"
),
),
url
(
url
(
r'^v1/preferences/
'
+
USERNAME_PATTERN
+
'/(?P<preference_key>[a-zA-Z0-9_]+)$'
,
r'^v1/preferences/
{}/(?P<preference_key>[a-zA-Z0-9_]+)$'
.
format
(
USERNAME_PATTERN
)
,
PreferencesDetailView
.
as_view
(),
PreferencesDetailView
.
as_view
(),
name
=
"preferences_detail_api"
name
=
"preferences_detail_api"
),
),
...
...
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