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
9d956e03
Commit
9d956e03
authored
Feb 25, 2015
by
Christina Roberts
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #7053 from edx/christina/account-api
User account API
parents
8bdd90a5
ae0333cb
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
503 additions
and
10 deletions
+503
-10
lms/djangoapps/mobile_api/testutils.py
+2
-2
lms/djangoapps/mobile_api/utils.py
+2
-8
lms/urls.py
+2
-0
openedx/core/djangoapps/user_api/accounts/__init__.py
+0
-0
openedx/core/djangoapps/user_api/accounts/serializers.py
+42
-0
openedx/core/djangoapps/user_api/accounts/tests/test_views.py
+248
-0
openedx/core/djangoapps/user_api/accounts/urls.py
+14
-0
openedx/core/djangoapps/user_api/accounts/views.py
+161
-0
openedx/core/lib/api/parsers.py
+8
-0
openedx/core/lib/api/permissions.py
+24
-0
No files found.
lms/djangoapps/mobile_api/testutils.py
View file @
9d956e03
...
...
@@ -105,7 +105,7 @@ class MobileAuthUserTestMixin(MobileAuthTestMixin):
"""
def
test_invalid_user
(
self
):
self
.
login_and_enroll
()
self
.
api_response
(
expected_response_code
=
40
3
,
username
=
'no_user'
)
self
.
api_response
(
expected_response_code
=
40
4
,
username
=
'no_user'
)
def
test_other_user
(
self
):
# login and enroll as the test user
...
...
@@ -120,7 +120,7 @@ class MobileAuthUserTestMixin(MobileAuthTestMixin):
# now login and call the API as the test user
self
.
login
()
self
.
api_response
(
expected_response_code
=
40
3
,
username
=
other
.
username
)
self
.
api_response
(
expected_response_code
=
40
4
,
username
=
other
.
username
)
@ddt.ddt
...
...
lms/djangoapps/mobile_api/utils.py
View file @
9d956e03
...
...
@@ -10,6 +10,7 @@ from util.authentication import SessionAuthenticationAllowInactiveUser, OAuth2Au
from
opaque_keys.edx.keys
import
CourseKey
from
xmodule.modulestore.django
import
modulestore
from
courseware.courses
import
get_course_with_access
from
openedx.core.lib.api.permissions
import
IsUserInUrl
def
mobile_course_access
(
depth
=
0
,
verify_enrolled
=
True
):
...
...
@@ -42,13 +43,6 @@ def mobile_view(is_user=False):
"""
Function and class decorator that abstracts the authentication and permission checks for mobile api views.
"""
class
IsUser
(
permissions
.
BasePermission
):
"""
Permission that checks to see if the request user matches the user in the URL.
"""
def
has_permission
(
self
,
request
,
view
):
return
request
.
user
.
username
==
request
.
parser_context
.
get
(
'kwargs'
,
{})
.
get
(
'username'
,
None
)
def
_decorator
(
func_or_class
):
"""
Requires either OAuth2 or Session-based authentication.
...
...
@@ -60,6 +54,6 @@ def mobile_view(is_user=False):
)
func_or_class
.
permission_classes
=
(
permissions
.
IsAuthenticated
,)
if
is_user
:
func_or_class
.
permission_classes
+=
(
IsUser
,)
func_or_class
.
permission_classes
+=
(
IsUser
InUrl
,)
return
func_or_class
return
_decorator
lms/urls.py
View file @
9d956e03
...
...
@@ -61,6 +61,8 @@ urlpatterns = ('', # nopep8
url
(
r'^user_api/'
,
include
(
'openedx.core.djangoapps.user_api.urls'
)),
url
(
r'^api/user/'
,
include
(
'openedx.core.djangoapps.user_api.accounts.urls'
)),
url
(
r'^notifier_api/'
,
include
(
'notifier_api.urls'
)),
url
(
r'^lang_pref/'
,
include
(
'lang_pref.urls'
)),
...
...
openedx/core/djangoapps/user_api/accounts/__init__.py
0 → 100644
View file @
9d956e03
openedx/core/djangoapps/user_api/accounts/serializers.py
0 → 100644
View file @
9d956e03
from
rest_framework
import
serializers
from
django.contrib.auth.models
import
User
from
student.models
import
UserProfile
class
AccountUserSerializer
(
serializers
.
HyperlinkedModelSerializer
):
"""
Class that serializes the portion of User model needed for account information.
"""
class
Meta
:
model
=
User
fields
=
(
"username"
,
"email"
,
"date_joined"
)
read_only_fields
=
(
"username"
,
"email"
,
"date_joined"
)
class
AccountLegacyProfileSerializer
(
serializers
.
HyperlinkedModelSerializer
):
"""
Class that serializes the portion of UserProfile model needed for account information.
"""
class
Meta
:
model
=
UserProfile
fields
=
(
"name"
,
"gender"
,
"goals"
,
"year_of_birth"
,
"level_of_education"
,
"language"
,
"country"
,
"mailing_address"
)
read_only_fields
=
(
"name"
,)
def
transform_gender
(
self
,
obj
,
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
):
""" 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
):
""" Converts empty string to None, to indicate not set. Replaced by to_representation in version 3. """
return
AccountLegacyProfileSerializer
.
convert_empty_to_None
(
value
)
@staticmethod
def
convert_empty_to_None
(
value
):
""" Helper method to convert empty string to None (other values pass through). """
return
None
if
value
==
""
else
value
openedx/core/djangoapps/user_api/accounts/tests/test_views.py
0 → 100644
View file @
9d956e03
import
unittest
import
ddt
import
json
from
datetime
import
datetime
from
django.test
import
TestCase
from
django.core.urlresolvers
import
reverse
from
django.conf
import
settings
from
rest_framework.test
import
APITestCase
,
APIClient
from
student.tests.factories
import
UserFactory
from
student.models
import
UserProfile
TEST_PASSWORD
=
"test"
@ddt.ddt
@unittest.skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Test only valid in lms'
)
class
TestAccountAPI
(
APITestCase
):
def
setUp
(
self
):
super
(
TestAccountAPI
,
self
)
.
setUp
()
self
.
anonymous_client
=
APIClient
()
self
.
different_user
=
UserFactory
.
create
(
password
=
TEST_PASSWORD
)
self
.
different_client
=
APIClient
()
self
.
staff_user
=
UserFactory
(
is_staff
=
True
,
password
=
TEST_PASSWORD
)
self
.
staff_client
=
APIClient
()
self
.
user
=
UserFactory
.
create
(
password
=
TEST_PASSWORD
)
self
.
url
=
reverse
(
"accounts_api"
,
kwargs
=
{
'username'
:
self
.
user
.
username
})
def
test_get_account_anonymous_user
(
self
):
"""
Test that an anonymous client (not logged in) cannot call get.
"""
self
.
send_get
(
self
.
anonymous_client
,
expected_status
=
401
)
def
test_get_account_different_user
(
self
):
"""
Test that a client (logged in) cannot get the account information for a different client.
"""
self
.
different_client
.
login
(
username
=
self
.
different_user
.
username
,
password
=
TEST_PASSWORD
)
self
.
send_get
(
self
.
different_client
,
expected_status
=
404
)
def
test_get_account_default
(
self
):
"""
Test that a client (logged in) can get her own account information (using default legacy profile information,
as created by the test UserFactory).
"""
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
TEST_PASSWORD
)
response
=
self
.
send_get
(
self
.
client
)
data
=
response
.
data
self
.
assertEqual
(
11
,
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"
):
self
.
assertIsNone
(
data
[
empty_field
])
self
.
assertIsNone
(
data
[
"country"
])
# TODO: what should the format of this be?
self
.
assertEqual
(
""
,
data
[
"language"
])
self
.
assertEqual
(
"m"
,
data
[
"gender"
])
self
.
assertEqual
(
"World domination"
,
data
[
"goals"
])
self
.
assertEqual
(
self
.
user
.
email
,
data
[
"email"
])
self
.
assertIsNotNone
(
data
[
"date_joined"
])
@ddt.data
(
(
"client"
,
"user"
),
(
"staff_client"
,
"staff_user"
),
)
@ddt.unpack
def
test_get_account
(
self
,
api_client
,
user
):
"""
Test that a client (logged in) can get her own account information. Also verifies that a "is_staff"
user can get the account information for other users.
"""
# Create some test profile values.
legacy_profile
=
UserProfile
.
objects
.
get
(
id
=
self
.
user
.
id
)
legacy_profile
.
country
=
"US"
legacy_profile
.
level_of_education
=
"m"
legacy_profile
.
year_of_birth
=
1900
legacy_profile
.
goals
=
"world peace"
legacy_profile
.
mailing_address
=
"Park Ave"
legacy_profile
.
save
()
client
=
self
.
login_client
(
api_client
,
user
)
response
=
self
.
send_get
(
client
)
data
=
response
.
data
self
.
assertEqual
(
11
,
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
(
1900
,
data
[
"year_of_birth"
])
self
.
assertEqual
(
"m"
,
data
[
"level_of_education"
])
self
.
assertEqual
(
"world peace"
,
data
[
"goals"
])
self
.
assertEqual
(
"Park Ave"
,
data
[
'mailing_address'
])
self
.
assertEqual
(
self
.
user
.
email
,
data
[
"email"
])
self
.
assertIsNotNone
(
data
[
"date_joined"
])
def
test_get_account_empty_string
(
self
):
"""
Test the conversion of empty strings to None for certain fields.
"""
legacy_profile
=
UserProfile
.
objects
.
get
(
id
=
self
.
user
.
id
)
legacy_profile
.
country
=
""
legacy_profile
.
level_of_education
=
""
legacy_profile
.
gender
=
""
legacy_profile
.
save
()
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
TEST_PASSWORD
)
response
=
self
.
send_get
(
self
.
client
)
for
empty_field
in
(
"level_of_education"
,
"gender"
,
"country"
):
self
.
assertIsNone
(
response
.
data
[
empty_field
])
@ddt.data
(
(
"client"
,
"user"
,
"gender"
,
"f"
,
"not a gender"
,
"Select a valid choice. not a gender is not one of the available choices."
),
(
"client"
,
"user"
,
"level_of_education"
,
"none"
,
"x"
,
"Select a valid choice. x is not one of the available choices."
),
(
"client"
,
"user"
,
"country"
,
"GB"
,
"XY"
,
"Select a valid choice. XY is not one of the available choices."
),
(
"client"
,
"user"
,
"year_of_birth"
,
2009
,
"not_an_int"
,
"Enter a whole number."
),
(
"client"
,
"user"
,
"language"
,
"Creole"
),
(
"client"
,
"user"
,
"goals"
,
"Smell the roses"
),
(
"client"
,
"user"
,
"mailing_address"
,
"Sesame Street"
),
# All of the fields can be edited by is_staff, but iterating through all of them again seems like overkill.
# Just test a representative field.
(
"staff_client"
,
"staff_user"
,
"goals"
,
"Smell the roses"
),
)
@ddt.unpack
def
test_patch_account
(
self
,
api_client
,
user
,
field
,
value
,
fails_validation_value
=
None
,
developer_validation_message
=
None
):
"""
Test the behavior of patch, when using the correct content_type.
"""
client
=
self
.
login_client
(
api_client
,
user
)
self
.
send_patch
(
client
,
{
field
:
value
})
get_response
=
self
.
send_get
(
client
)
self
.
assertEqual
(
value
,
get_response
.
data
[
field
])
if
fails_validation_value
:
error_response
=
self
.
send_patch
(
client
,
{
field
:
fails_validation_value
},
expected_status
=
400
)
self
.
assertEqual
(
"Value '{0}' is not valid for field '{1}'."
.
format
(
fails_validation_value
,
field
),
error_response
.
data
[
"field_errors"
][
field
][
"user_message"
]
)
self
.
assertEqual
(
developer_validation_message
,
error_response
.
data
[
"field_errors"
][
field
][
"developer_message"
]
)
else
:
# If there are no values that would fail validation, then empty string should be supported.
self
.
send_patch
(
client
,
{
field
:
""
})
get_response
=
self
.
send_get
(
client
)
self
.
assertEqual
(
""
,
get_response
.
data
[
field
])
@ddt.data
(
(
"client"
,
"user"
),
(
"staff_client"
,
"staff_user"
),
)
@ddt.unpack
def
test_patch_account_noneditable
(
self
,
api_client
,
user
):
"""
Tests the behavior of patch when a read-only field is attempted to be edited.
"""
client
=
self
.
login_client
(
api_client
,
user
)
def
verify_error_response
(
field_name
,
data
):
self
.
assertEqual
(
"This field is not editable via this API"
,
data
[
"field_errors"
][
field_name
][
"developer_message"
]
)
self
.
assertEqual
(
"Field '{0}' cannot be edited."
.
format
(
field_name
),
data
[
"field_errors"
][
field_name
][
"user_message"
]
)
for
field_name
in
[
"username"
,
"email"
,
"date_joined"
,
"name"
]:
response
=
self
.
send_patch
(
client
,
{
field_name
:
"will_error"
,
"gender"
:
"f"
},
expected_status
=
400
)
verify_error_response
(
field_name
,
response
.
data
)
# Make sure that gender did not change.
response
=
self
.
send_get
(
client
)
self
.
assertEqual
(
"m"
,
response
.
data
[
"gender"
])
# Test error message with multiple read-only items
response
=
self
.
send_patch
(
client
,
{
"username"
:
"will_error"
,
"email"
:
"xx"
},
expected_status
=
400
)
self
.
assertEqual
(
2
,
len
(
response
.
data
[
"field_errors"
]))
verify_error_response
(
"username"
,
response
.
data
)
verify_error_response
(
"email"
,
response
.
data
)
def
test_patch_bad_content_type
(
self
):
"""
Test the behavior of patch when an incorrect content_type is specified.
"""
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
TEST_PASSWORD
)
self
.
send_patch
(
self
.
client
,
{},
content_type
=
"application/json"
,
expected_status
=
415
)
self
.
send_patch
(
self
.
client
,
{},
content_type
=
"application/xml"
,
expected_status
=
415
)
def
test_patch_account_empty_string
(
self
):
"""
Tests the behavior of patch when attempting to set fields with a select list of options to the empty string.
Also verifies the behaviour when setting to None.
"""
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
TEST_PASSWORD
)
for
field_name
in
[
"gender"
,
"level_of_education"
,
"country"
]:
self
.
send_patch
(
self
.
client
,
{
field_name
:
""
})
response
=
self
.
send_get
(
self
.
client
)
# Although throwing a 400 might be reasonable, the default DRF behavior with ModelSerializer
# is to convert to None, which also seems acceptable (and is difficult to override).
self
.
assertIsNone
(
response
.
data
[
field_name
])
# Verify that the behavior is the same for sending None.
self
.
send_patch
(
self
.
client
,
{
field_name
:
""
})
response
=
self
.
send_get
(
self
.
client
)
self
.
assertIsNone
(
response
.
data
[
field_name
])
def
login_client
(
self
,
api_client
,
user
):
"""Helper method for getting the client and user and logging in. Returns client. """
client
=
getattr
(
self
,
api_client
)
user
=
getattr
(
self
,
user
)
client
.
login
(
username
=
user
.
username
,
password
=
TEST_PASSWORD
)
return
client
def
send_patch
(
self
,
client
,
json_data
,
content_type
=
"application/merge-patch+json"
,
expected_status
=
204
):
"""
Helper method for sending a patch to the server, defaulting to application/merge-patch+json content_type.
Verifies the expected status and returns the response.
"""
response
=
client
.
patch
(
self
.
url
,
data
=
json
.
dumps
(
json_data
),
content_type
=
content_type
)
self
.
assertEqual
(
expected_status
,
response
.
status_code
)
return
response
def
send_get
(
self
,
client
,
expected_status
=
200
):
"""
Helper method for sending a GET to the server. Verifies the expected status and returns the response.
"""
response
=
client
.
get
(
self
.
url
)
self
.
assertEqual
(
expected_status
,
response
.
status_code
)
return
response
openedx/core/djangoapps/user_api/accounts/urls.py
0 → 100644
View file @
9d956e03
from
.views
import
AccountView
from
django.conf.urls
import
include
,
patterns
,
url
USERNAME_PATTERN
=
r'(?P<username>[\w.+-]+)'
urlpatterns
=
patterns
(
''
,
url
(
r'^v0/accounts/'
+
USERNAME_PATTERN
+
'$'
,
AccountView
.
as_view
(),
name
=
"accounts_api"
)
)
openedx/core/djangoapps/user_api/accounts/views.py
0 → 100644
View file @
9d956e03
"""
NOTE: this API is WIP and has not yet been approved. Do not use this API without talking to Christina or Andy.
For more information, see:
https://openedx.atlassian.net/wiki/display/TNL/User+API
"""
from
django.core.exceptions
import
ObjectDoesNotExist
from
django.contrib.auth.models
import
User
from
django.utils.translation
import
ugettext
as
_
from
rest_framework.views
import
APIView
from
rest_framework.response
import
Response
from
rest_framework
import
status
from
rest_framework.authentication
import
OAuth2Authentication
,
SessionAuthentication
from
rest_framework
import
permissions
from
rest_framework
import
parsers
from
student.models
import
UserProfile
from
openedx.core.djangoapps.user_api.accounts.serializers
import
AccountLegacyProfileSerializer
,
AccountUserSerializer
from
openedx.core.lib.api.permissions
import
IsUserInUrlOrStaff
from
openedx.core.lib.api.parsers
import
MergePatchParser
class
AccountView
(
APIView
):
"""
**Use Cases**
Get or update the user's account information. Updates are only supported through merge patch.
**Example Requests**:
GET /api/user/v0/accounts/{username}/
PATCH /api/user/v0/accounts/{username}/ with content_type "application/merge-patch+json"
**Response Values for GET**
* username: username associated with the account (not editable)
* name: full name of the user (not editable through this API)
* email: email for the user (not editable through this API)
* date_joined: date this account was created (not editable), in the string format provided by
datetime (for example, "2014-08-26T17:52:11Z")
* gender: null (not set), "m", "f", or "o"
* year_of_birth: null or integer year
* level_of_education: null (not set), or one of the following choices:
* "p" signifying "Doctorate"
* "m" signifying "Master's or professional degree"
* "b" signifying "Bachelor's degree"
* "a" signifying "Associate's degree"
* "hs" signifying "Secondary/high school"
* "jhs" signifying "Junior secondary/junior high/middle school"
* "el" signifying "Elementary/primary school"
* "none" signifying "None"
* "o" signifying "Other"
* language: null or name of preferred language
* country: null (not set), or a Country corresponding to one of the ISO 3166-1 countries
* mailing_address: null or textual representation of mailing address
* goals: null or textual representation of goals
**Response for PATCH**
Returns a 204 status if successful, with no additional content.
If "application/merge-patch+json" is not the specified content_type, returns a 415 status.
"""
authentication_classes
=
(
OAuth2Authentication
,
SessionAuthentication
)
permission_classes
=
(
permissions
.
IsAuthenticated
,
IsUserInUrlOrStaff
)
parser_classes
=
(
MergePatchParser
,)
def
get
(
self
,
request
,
username
):
"""
GET /api/user/v0/accounts/{username}/
"""
existing_user
,
existing_user_profile
=
self
.
_get_user_and_profile
(
username
)
user_serializer
=
AccountUserSerializer
(
existing_user
)
legacy_profile_serializer
=
AccountLegacyProfileSerializer
(
existing_user_profile
)
return
Response
(
dict
(
user_serializer
.
data
,
**
legacy_profile_serializer
.
data
))
def
patch
(
self
,
request
,
username
):
"""
PATCH /api/user/v0/accounts/{username}/
Note that this implementation is the "merge patch" implementation proposed in
https://tools.ietf.org/html/rfc7396. The content_type must be "application/merge-patch+json" or
else an error response with status code 415 will be returned.
"""
existing_user
,
existing_user_profile
=
self
.
_get_user_and_profile
(
username
)
# Check for fields that are not editable. Marking them read-only causes them to be ignored, but we wish to 400.
update
=
request
.
DATA
read_only_fields
=
set
(
update
.
keys
())
.
intersection
(
AccountUserSerializer
.
Meta
.
read_only_fields
+
AccountLegacyProfileSerializer
.
Meta
.
read_only_fields
)
if
read_only_fields
:
field_errors
=
{}
for
read_only_field
in
read_only_fields
:
field_errors
[
read_only_field
]
=
{
"developer_message"
:
"This field is not editable via this API"
,
"user_message"
:
_
(
"Field '{field_name}' cannot be edited."
.
format
(
field_name
=
read_only_field
))
}
response_data
=
{
"field_errors"
:
field_errors
}
return
Response
(
response_data
,
status
=
status
.
HTTP_400_BAD_REQUEST
)
user_serializer
=
AccountUserSerializer
(
existing_user
,
data
=
update
)
legacy_profile_serializer
=
AccountLegacyProfileSerializer
(
existing_user_profile
,
data
=
update
)
for
serializer
in
user_serializer
,
legacy_profile_serializer
:
validation_errors
=
self
.
_get_validation_errors
(
update
,
serializer
)
if
validation_errors
:
return
Response
(
validation_errors
,
status
=
status
.
HTTP_400_BAD_REQUEST
)
serializer
.
save
()
return
Response
(
status
=
status
.
HTTP_204_NO_CONTENT
)
def
_get_user_and_profile
(
self
,
username
):
"""
Helper method to return the legacy user and profile objects based on username.
"""
try
:
existing_user
=
User
.
objects
.
get
(
username
=
username
)
except
ObjectDoesNotExist
:
return
Response
({},
status
=
status
.
HTTP_404_NOT_FOUND
)
existing_user_profile
=
UserProfile
.
objects
.
get
(
user
=
existing_user
)
return
existing_user
,
existing_user_profile
def
_get_validation_errors
(
self
,
update
,
serializer
):
"""
Helper method that returns any validation errors that are present.
"""
validation_errors
=
{}
if
not
serializer
.
is_valid
():
field_errors
=
{}
errors
=
serializer
.
errors
for
key
,
value
in
errors
.
iteritems
():
if
isinstance
(
value
,
list
)
and
len
(
value
)
>
0
:
developer_message
=
value
[
0
]
else
:
developer_message
=
"Invalid value: {field_value}'"
.
format
(
field_value
=
update
[
key
])
field_errors
[
key
]
=
{
"developer_message"
:
developer_message
,
"user_message"
:
_
(
"Value '{field_value}' is not valid for field '{field_name}'."
.
format
(
field_value
=
update
[
key
],
field_name
=
key
)
)
}
validation_errors
[
'field_errors'
]
=
field_errors
return
validation_errors
\ No newline at end of file
openedx/core/lib/api/parsers.py
0 → 100644
View file @
9d956e03
from
rest_framework
import
parsers
class
MergePatchParser
(
parsers
.
JSONParser
):
"""
Custom parser to be used with the "merge patch" implementation (https://tools.ietf.org/html/rfc7396).
"""
media_type
=
'application/merge-patch+json'
openedx/core/lib/api/permissions.py
View file @
9d956e03
from
django.conf
import
settings
from
rest_framework
import
permissions
from
rest_framework.exceptions
import
PermissionDenied
from
django.http
import
Http404
class
ApiKeyHeaderPermission
(
permissions
.
BasePermission
):
...
...
@@ -31,3 +32,26 @@ class IsAuthenticatedOrDebug(permissions.BasePermission):
user
=
getattr
(
request
,
'user'
,
None
)
return
user
and
user
.
is_authenticated
()
class
IsUserInUrl
(
permissions
.
BasePermission
):
"""
Permission that checks to see if the request user matches the user in the URL.
"""
def
has_permission
(
self
,
request
,
view
):
# Return a 404 instead of a 403 (Unauthorized). If one user is looking up
# other users, do not let them deduce the existence of an account.
if
request
.
user
.
username
!=
request
.
parser_context
.
get
(
'kwargs'
,
{})
.
get
(
'username'
,
None
):
raise
Http404
()
return
True
class
IsUserInUrlOrStaff
(
IsUserInUrl
):
"""
Permission that checks to see if the request user matches the user in the URL or has is_staff access.
"""
def
has_permission
(
self
,
request
,
view
):
if
request
.
user
.
is_staff
:
return
True
return
super
(
IsUserInUrlOrStaff
,
self
)
.
has_permission
(
request
,
view
)
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