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
3c64c86e
Commit
3c64c86e
authored
May 19, 2014
by
Matt Drayer
Committed by
Jonathan Piacenti
Aug 20, 2015
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
mattdrayer/api-userscleanup: Added docstrings and changed to fail-fast style
parent
2a923bdc
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
317 additions
and
192 deletions
+317
-192
lms/djangoapps/api_manager/users/tests.py
+3
-3
lms/djangoapps/api_manager/users/views.py
+314
-189
No files found.
lms/djangoapps/api_manager/users/tests.py
View file @
3c64c86e
...
...
@@ -126,7 +126,7 @@ class UsersApiTests(TestCase):
response
=
self
.
do_post
(
test_uri
,
data
)
self
.
assertEqual
(
response
.
status_code
,
409
)
self
.
assertGreater
(
response
.
data
[
'message'
],
0
)
self
.
assertEqual
(
response
.
data
[
'field_conflict'
],
'username'
)
self
.
assertEqual
(
response
.
data
[
'field_conflict'
],
'username
or email
'
)
def
test_user_detail_get
(
self
):
test_uri
=
'/api/users'
...
...
@@ -582,7 +582,7 @@ class UsersApiTests(TestCase):
display_name
=
"Chapter 1"
)
user_id
=
2342334
course_id
=
'asd
fa
9sd8fasdf'
course_id
=
'asd
/fa/
9sd8fasdf'
test_uri
=
'/api/users/{}/courses/{}'
.
format
(
str
(
user_id
),
course_id
)
position_data
=
{
'position'
:
{
...
...
@@ -661,7 +661,7 @@ class UsersApiTests(TestCase):
self
.
assertEqual
(
response
.
data
[
'position'
],
chapter1
.
id
)
def
test_user_courses_detail_get_undefined_user
(
self
):
test_uri
=
'/api/users/2134234/courses/a8df7
asv
d98'
test_uri
=
'/api/users/2134234/courses/a8df7
/asv/
d98'
response
=
self
.
do_get
(
test_uri
)
self
.
assertEqual
(
response
.
status_code
,
404
)
...
...
lms/djangoapps/api_manager/users/views.py
View file @
3c64c86e
...
...
@@ -112,11 +112,48 @@ def _save_content_position(request, user, course_id, course_descriptor, position
class
UsersList
(
SecureAPIView
):
""" Inherit with SecureAPIView """
"""
### The UsersList view allows clients to retrieve/append a list of User entities
- URI: ```/api/users/```
- POST: Provides the ability to append to the User entity set
* email: __required__, The unique email address for the User being created
* username: __required__, The unique username for the User being created
* password: __required__, String which matches enabled formatting constraints
* first_name
* last_name
* is_active, Boolean flag controlling the User's account activation status
* is_staff, Boolean flag controlling the User's administrative access/permissions
* city
* country, Two-character country code
* level_of_education
* year_of_birth, Four-digit integer value
* gender, Single-character value (M/F)
- POST Example:
{
"email" : "honor@edx.org",
"username" : "honor",
"password" : "edx!@#",
"first_name" : "Honor",
"last_name" : "Student",
"is_active" : False,
"is_staff" : False,
"city" : "Boston",
"country" : "US",
"level_of_education" : "hs",
"year_of_birth" : "1996",
"gender" : "F",
}
### Use Cases/Notes:
* GET requests for _all_ users are not currently allowed via the API
* Password formatting policies can be enabled through the "ENFORCE_PASSWORD_POLICY" feature flag
* The first_name and last_name fields are additionally concatenated and stored in the 'name' field of UserProfile
* Values for level_of_education can be found in the LEVEL_OF_EDUCATION_CHOICES enum, located in common/student/models.py
"""
def
post
(
self
,
request
):
"""
POST
creates a new user in the system
POST
/api/users/
"""
response_data
=
{}
base_uri
=
_generate_base_uri
(
request
)
...
...
@@ -139,108 +176,143 @@ class UsersList(SecureAPIView):
validate_password_complexity
(
password
)
validate_password_dictionary
(
password
)
except
ValidationError
,
err
:
status_code
=
status
.
HTTP_400_BAD_REQUEST
response_data
[
'message'
]
=
_
(
'Password: '
)
+
'; '
.
join
(
err
.
messages
)
return
Response
(
response_data
,
status
=
status
_code
)
return
Response
(
response_data
,
status
=
status
.
HTTP_400_BAD_REQUEST
)
try
:
validate_email
(
email
)
except
ValidationError
:
status_code
=
status
.
HTTP_400_BAD_REQUEST
response_data
[
'message'
]
=
_
(
'Valid e-mail is required.'
)
return
Response
(
response_data
,
status
=
status
_code
)
return
Response
(
response_data
,
status
=
status
.
HTTP_400_BAD_REQUEST
)
try
:
validate_slug
(
username
)
except
ValidationError
:
status_code
=
status
.
HTTP_400_BAD_REQUEST
response_data
[
'message'
]
=
_
(
'Username should only consist of A-Z and 0-9, with no spaces.'
)
return
Response
(
response_data
,
status
=
status
_code
)
return
Response
(
response_data
,
status
=
status
.
HTTP_400_BAD_REQUEST
)
# Create the User, UserProfile, and UserPreference records
try
:
user
=
User
.
objects
.
create
(
email
=
email
,
username
=
username
,
is_staff
=
is_staff
)
except
IntegrityError
:
user
=
None
else
:
user
.
set_password
(
password
)
user
.
first_name
=
first_name
user
.
last_name
=
last_name
if
is_active
is
not
None
:
user
.
is_active
=
is_active
if
is_staff
is
not
None
:
user
.
is_staff
=
is_staff
user
.
save
()
profile
=
UserProfile
(
user
=
user
)
profile
.
name
=
'{} {}'
.
format
(
first_name
,
last_name
)
profile
.
city
=
city
profile
.
country
=
country
profile
.
level_of_education
=
level_of_education
profile
.
gender
=
gender
response_data
[
'message'
]
=
"User '
%
s' already exists"
%
(
username
)
response_data
[
'field_conflict'
]
=
"username or email"
return
Response
(
response_data
,
status
=
status
.
HTTP_409_CONFLICT
)
try
:
profile
.
year_of_birth
=
int
(
year_of_birth
)
except
ValueError
:
# If they give us garbage, just ignore it instead
# of asking them to put an integer.
profile
.
year_of_birth
=
None
user
.
set_password
(
password
)
user
.
first_name
=
first_name
user
.
last_name
=
last_name
if
is_active
is
not
None
:
user
.
is_active
=
is_active
if
is_staff
is
not
None
:
user
.
is_staff
=
is_staff
user
.
save
()
profile
.
save
()
profile
=
UserProfile
(
user
=
user
)
profile
.
name
=
'{} {}'
.
format
(
first_name
,
last_name
)
profile
.
city
=
city
profile
.
country
=
country
profile
.
level_of_education
=
level_of_education
profile
.
gender
=
gender
UserPreference
.
set_preference
(
user
,
LANGUAGE_KEY
,
get_language
())
try
:
profile
.
year_of_birth
=
int
(
year_of_birth
)
except
ValueError
:
# If they give us garbage, just ignore it instead
# of asking them to put an integer.
profile
.
year_of_birth
=
None
# add this account creation to password history
# NOTE, this will be a NOP unless the feature has been turned on in configuration
password_history_entry
=
PasswordHistory
()
password_history_entry
.
create
(
user
)
profile
.
save
()
# add to audit log
AUDIT_LOG
.
info
(
u"API::New account created with user-id - {0}"
.
format
(
user
.
id
))
UserPreference
.
set_preference
(
user
,
LANGUAGE_KEY
,
get_language
())
# CDODGE: @TODO: We will have to extend this to look in the CourseEnrollmentAllowed table and
# auto-enroll students when they create a new account. Also be sure to remove from
# the CourseEnrollmentAllow table after the auto-registration has taken place
if
user
:
status_code
=
status
.
HTTP_201_CREATED
response_data
=
_serialize_user
(
response_data
,
user
)
response_data
[
'uri'
]
=
'{}/{}'
.
format
(
base_uri
,
str
(
user
.
id
))
else
:
status_code
=
status
.
HTTP_409_CONFLICT
response_data
[
'message'
]
=
"User '
%
s' already exists"
%
(
username
)
response_data
[
'field_conflict'
]
=
"username"
# add this account creation to password history
# NOTE, this will be a NOP unless the feature has been turned on in configuration
password_history_entry
=
PasswordHistory
()
password_history_entry
.
create
(
user
)
# add to audit log
AUDIT_LOG
.
info
(
u"API::New account created with user-id - {0}"
.
format
(
user
.
id
))
return
Response
(
response_data
,
status
=
status_code
)
# CDODGE: @TODO: We will have to extend this to look in the CourseEnrollmentAllowed table and
# auto-enroll students when they create a new account. Also be sure to remove from
# the CourseEnrollmentAllow table after the auto-registration has taken place
response_data
=
_serialize_user
(
response_data
,
user
)
response_data
[
'uri'
]
=
'{}/{}'
.
format
(
base_uri
,
str
(
user
.
id
))
return
Response
(
response_data
,
status
=
status
.
HTTP_201_CREATED
)
class
UsersDetail
(
SecureAPIView
):
""" Inherit with SecureAPIView """
"""
### The UsersDetail view allows clients to interact with a specific User entity
- URI: ```/api/users/{user_id}```
- GET: Returns a JSON representation of the specified User entity
- POST: Provides the ability to modify specific fields for this User entity
* email: __required__, The unique email address for the User being created
* username: __required__, The unique username for the User being created
* password: __required__, String which matches enabled formatting constraints
* first_name
* last_name
* is_active, Boolean flag controlling the User's account activation status
* is_staff, Boolean flag controlling the User's administrative access/permissions
* city
* country, Two-character country code
* level_of_education
* year_of_birth, Four-digit integer value
* gender, Single-character value (M/F)
- POST Example:
{
"email" : "honor@edx.org",
"username" : "honor",
"password" : "edx!@#",
"first_name" : "Honor",
"last_name" : "Student",
"is_active" : False,
"is_staff" : False,
"city" : "Boston",
"country" : "US",
"level_of_education" : "hs",
"year_of_birth" : "1996",
"gender" : "F",
}
### Use Cases/Notes:
* Use the UsersDetail view to obtain the current state for a specific User
* For POSTs, you may include only those parameters you wish to modify, for example:
** Modifying the 'city' without changing the 'level_of_education' field
** New passwords will be subject to both format and history checks, if enabled
* A GET response will additionally include a list of URIs to available sub-resources:
** Related Courses (/api/users/{user_id}/courses)
** Related Groups(/api/users/{user_id}/groups)
"""
def
get
(
self
,
request
,
user_id
):
"""
GET
retrieves an existing user from the system
GET
/api/users/{user_id}
"""
response_data
=
{}
base_uri
=
_generate_base_uri
(
request
)
try
:
existing_user
=
User
.
objects
.
get
(
id
=
user_id
)
_serialize_user
(
response_data
,
existing_user
)
response_data
[
'uri'
]
=
base_uri
response_data
[
'resources'
]
=
[]
resource_uri
=
'{}/groups'
.
format
(
base_uri
)
response_data
[
'resources'
]
.
append
({
'uri'
:
resource_uri
})
resource_uri
=
'{}/courses'
.
format
(
base_uri
)
response_data
[
'resources'
]
.
append
({
'uri'
:
resource_uri
})
existing_user_profile
=
UserProfile
.
objects
.
get
(
user_id
=
user_id
)
if
existing_user_profile
:
_serialize_user_profile
(
response_data
,
existing_user_profile
)
return
Response
(
response_data
,
status
=
status
.
HTTP_200_OK
)
except
ObjectDoesNotExist
:
return
Response
(
response_data
,
status
=
status
.
HTTP_404_NOT_FOUND
)
_serialize_user
(
response_data
,
existing_user
)
response_data
[
'uri'
]
=
base_uri
response_data
[
'resources'
]
=
[]
resource_uri
=
'{}/groups'
.
format
(
base_uri
)
response_data
[
'resources'
]
.
append
({
'uri'
:
resource_uri
})
resource_uri
=
'{}/courses'
.
format
(
base_uri
)
response_data
[
'resources'
]
.
append
({
'uri'
:
resource_uri
})
existing_user_profile
=
UserProfile
.
objects
.
get
(
user_id
=
user_id
)
if
existing_user_profile
:
_serialize_user_profile
(
response_data
,
existing_user_profile
)
return
Response
(
response_data
,
status
=
status
.
HTTP_200_OK
)
def
post
(
self
,
request
,
user_id
):
"""
POST
provides the ability to update information about an existing user
POST
/api/users/{user_id}
"""
response_data
=
{}
base_uri
=
_generate_base_uri
(
request
)
...
...
@@ -281,16 +353,14 @@ class UsersDetail(SecureAPIView):
try
:
validate_slug
(
username
)
except
ValidationError
:
status_code
=
status
.
HTTP_400_BAD_REQUEST
response_data
[
'message'
]
=
_
(
'Username should only consist of A-Z and 0-9, with no spaces.'
)
return
Response
(
response_data
,
status
=
status
_code
)
return
Response
(
response_data
,
status
=
status
.
HTTP_400_BAD_REQUEST
)
existing_username
=
User
.
objects
.
filter
(
username
=
username
)
.
filter
(
~
Q
(
id
=
user_id
))
if
existing_username
:
status_code
=
status
.
HTTP_409_CONFLICT
response_data
[
'message'
]
=
"User '
%
s' already exists"
%
(
username
)
response_data
[
'field_conflict'
]
=
"username"
return
Response
(
response_data
,
status
=
status
_code
)
return
Response
(
response_data
,
status
=
status
.
HTTP_409_CONFLICT
)
existing_user
.
username
=
username
response_data
[
'username'
]
=
existing_user
.
username
...
...
@@ -308,9 +378,8 @@ class UsersDetail(SecureAPIView):
except
ValidationError
,
err
:
# bad user? tick the rate limiter counter
AUDIT_LOG
.
warning
(
"API::Bad password in password_reset."
)
status_code
=
status
.
HTTP_400_BAD_REQUEST
response_data
[
'message'
]
=
_
(
'Password: '
)
+
'; '
.
join
(
err
.
messages
)
return
Response
(
response_data
,
status
=
status
_code
)
return
Response
(
response_data
,
status
=
status
.
HTTP_400_BAD_REQUEST
)
# also, check the password reuse policy
err_msg
=
None
if
not
PasswordHistory
.
is_allowable_password_reuse
(
existing_user
,
password
):
...
...
@@ -378,10 +447,27 @@ class UsersDetail(SecureAPIView):
class
UsersGroupsList
(
SecureAPIView
):
""" Inherit with SecureAPIView """
"""
### The UsersGroupsList view allows clients to interact with the set of Group entities related to the specified User
- URI: ```/api/users/{user_id}/groups/```
- GET: Returns a JSON representation (array) of the set of related Group entities
* type: Set filtering parameter
- POST: Append a Group entity to the set of related Group entities for the specified User
* group_id: __required__, The identifier for the Group being added
- POST Example:
{
"group_id" : 123
}
### Use Cases/Notes:
* Use the UsersGroupsList view to manage Group membership for a specific User
* For example, you could display a list of all of a User's groups in a dashboard or administrative view
* Optionally include the 'type' parameter to retrieve a subset of groups with a matching 'group_type' value
"""
def
post
(
self
,
request
,
user_id
):
"""
POST
creates a new user-group relationship in the system
POST
/api/users/{user_id}/groups
"""
response_data
=
{}
group_id
=
request
.
DATA
[
'group_id'
]
...
...
@@ -391,30 +477,22 @@ class UsersGroupsList(SecureAPIView):
existing_user
=
User
.
objects
.
get
(
id
=
user_id
)
existing_group
=
Group
.
objects
.
get
(
id
=
group_id
)
except
ObjectDoesNotExist
:
existing_user
=
None
existing_group
=
None
if
existing_user
and
existing_group
:
try
:
existing_relationship
=
existing_user
.
groups
.
get
(
id
=
existing_group
.
id
)
except
ObjectDoesNotExist
:
existing_relationship
=
None
if
existing_relationship
is
None
:
existing_user
.
groups
.
add
(
existing_group
.
id
)
response_data
[
'uri'
]
=
'{}/{}'
.
format
(
base_uri
,
existing_user
.
id
)
response_data
[
'group_id'
]
=
str
(
existing_group
.
id
)
response_data
[
'user_id'
]
=
str
(
existing_user
.
id
)
response_status
=
status
.
HTTP_201_CREATED
else
:
response_data
[
'uri'
]
=
'{}/{}'
.
format
(
base_uri
,
existing_group
.
id
)
response_data
[
'message'
]
=
"Relationship already exists."
response_status
=
status
.
HTTP_409_CONFLICT
else
:
response_status
=
status
.
HTTP_404_NOT_FOUND
return
Response
(
response_data
,
status
=
response_status
)
return
Response
({},
status
=
status
.
HTTP_404_NOT_FOUND
)
try
:
existing_relationship
=
existing_user
.
groups
.
get
(
id
=
existing_group
.
id
)
response_data
[
'uri'
]
=
'{}/{}'
.
format
(
base_uri
,
existing_group
.
id
)
response_data
[
'message'
]
=
"Relationship already exists."
return
Response
(
response_data
,
status
=
status
.
HTTP_409_CONFLICT
)
except
ObjectDoesNotExist
:
existing_user
.
groups
.
add
(
existing_group
.
id
)
response_data
[
'uri'
]
=
'{}/{}'
.
format
(
base_uri
,
existing_user
.
id
)
response_data
[
'group_id'
]
=
str
(
existing_group
.
id
)
response_data
[
'user_id'
]
=
str
(
existing_user
.
id
)
return
Response
(
response_data
,
status
=
status
.
HTTP_201_CREATED
)
def
get
(
self
,
request
,
user_id
):
"""
GET
retrieves the list of groups related to the specified user
GET
/api/users/{user_id}/groups?type=workgroup
"""
try
:
existing_user
=
User
.
objects
.
get
(
id
=
user_id
)
...
...
@@ -434,35 +512,38 @@ class UsersGroupsList(SecureAPIView):
group_data
[
'id'
]
=
group
.
id
group_data
[
'name'
]
=
group_profile
.
name
response_data
[
'groups'
]
.
append
(
group_data
)
response_status
=
status
.
HTTP_200_OK
return
Response
(
response_data
,
status
=
response_status
)
return
Response
(
response_data
,
status
=
status
.
HTTP_200_OK
)
class
UsersGroupsDetail
(
SecureAPIView
):
""" Inherit with SecureAPIView """
"""
### The UsersGroupsDetail view allows clients to interact with a specific User-Group relationship
- URI: ```/api/users/{user_id}/groups/{group_id}```
- GET: Returns a JSON representation of the specified User-Group relationship
- DELETE: Removes an existing User-Group relationship
### Use Cases/Notes:
* Use the UsersGroupsDetail to validate that a User is a member of a specific Group
* Cancelling a User's membership in a Group is as simple as calling DELETE on the URI
"""
def
get
(
self
,
request
,
user_id
,
group_id
):
"""
GET
retrieves an existing user-group relationship from the system
GET
/api/users/{user_id}/groups/{group_id}
"""
response_data
=
{}
try
:
existing_user
=
User
.
objects
.
get
(
id
=
user_id
,
is_active
=
True
)
existing_relationship
=
existing_user
.
groups
.
get
(
id
=
group_id
)
except
ObjectDoesNotExist
:
existing_user
=
None
existing_relationship
=
None
if
existing_user
and
existing_relationship
:
response_data
[
'user_id'
]
=
existing_user
.
id
response_data
[
'group_id'
]
=
existing_relationship
.
id
response_data
[
'uri'
]
=
_generate_base_uri
(
request
)
response_status
=
status
.
HTTP_200_OK
else
:
response_status
=
status
.
HTTP_404_NOT_FOUND
return
Response
(
response_data
,
status
=
response_status
)
return
Response
({},
status
=
status
.
HTTP_404_NOT_FOUND
)
response_data
[
'user_id'
]
=
existing_user
.
id
response_data
[
'group_id'
]
=
existing_relationship
.
id
response_data
[
'uri'
]
=
_generate_base_uri
(
request
)
return
Response
(
response_data
,
status
=
status
.
HTTP_200_OK
)
def
delete
(
self
,
request
,
user_id
,
group_id
):
"""
DELETE
removes/inactivates/etc. an existing user-group relationship
DELETE
/api/users/{user_id}/groups/{group_id}
"""
existing_user
=
User
.
objects
.
get
(
id
=
user_id
,
is_active
=
True
)
existing_user
.
groups
.
remove
(
group_id
)
...
...
@@ -471,10 +552,25 @@ class UsersGroupsDetail(SecureAPIView):
class
UsersCoursesList
(
SecureAPIView
):
""" Inherit with SecureAPIView """
"""
### The UsersCoursesList view allows clients to interact with the set of Course entities related to the specified User
- URI: ```/api/users/{user_id}/courses/```
- GET: Returns a JSON representation (array) of the set of related Course entities
- POST: Append a Group entity to the set of related Group entities for the specified User
* course_id: __required__, The identifier (aka, location/key) for the Course being added
- POST Example:
{
"course_id" : "edx/demo/course"
}
### Use Cases/Notes:
* POST to the UsersCoursesList view to create a new Course enrollment for the specified User (aka, Student)
* Perform a GET to generate a list of all active Course enrollments for the specified User
"""
def
post
(
self
,
request
,
user_id
):
"""
POST
creates a new course enrollment for a user
POST
/api/users/{user_id}/courses/
"""
store
=
modulestore
()
response_data
=
{}
...
...
@@ -484,53 +580,68 @@ class UsersCoursesList(SecureAPIView):
user
=
User
.
objects
.
get
(
id
=
user_id
)
course_descriptor
=
store
.
get_course
(
course_id
)
except
(
ObjectDoesNotExist
,
ValueError
):
user
=
None
course_descriptor
=
None
if
user
and
course_descriptor
:
base_uri
=
_generate_base_uri
(
request
)
course_enrollment
=
CourseEnrollment
.
enroll
(
user
,
course_id
)
response_data
[
'uri'
]
=
'{}/{}'
.
format
(
base_uri
,
course_id
)
response_data
[
'id'
]
=
course_id
response_data
[
'name'
]
=
course_descriptor
.
display_name
response_data
[
'is_active'
]
=
course_enrollment
.
is_active
status_code
=
status
.
HTTP_201_CREATED
else
:
status_code
=
status
.
HTTP_404_NOT_FOUND
return
Response
(
response_data
,
status
=
status_code
)
return
Response
({},
status
=
status
.
HTTP_404_NOT_FOUND
)
base_uri
=
_generate_base_uri
(
request
)
course_enrollment
=
CourseEnrollment
.
enroll
(
user
,
course_id
)
response_data
[
'uri'
]
=
'{}/{}'
.
format
(
base_uri
,
course_id
)
response_data
[
'id'
]
=
course_id
response_data
[
'name'
]
=
course_descriptor
.
display_name
response_data
[
'is_active'
]
=
course_enrollment
.
is_active
return
Response
(
response_data
,
status
=
status
.
HTTP_201_CREATED
)
def
get
(
self
,
request
,
user_id
):
"""
GET
creates the list of enrolled courses for a user
GET
/api/users/{user_id}/courses/
"""
store
=
modulestore
()
response_data
=
[]
base_uri
=
_generate_base_uri
(
request
)
try
:
user
=
User
.
objects
.
get
(
id
=
user_id
)
except
ObjectDoesNotExist
:
user
=
None
if
user
:
enrollments
=
CourseEnrollment
.
enrollments_for_user
(
user
=
user
)
for
enrollment
in
enrollments
:
descriptor
=
store
.
get_course
(
enrollment
.
course_id
)
course_data
=
{
"id"
:
enrollment
.
course_id
,
"uri"
:
'{}/{}'
.
format
(
base_uri
,
enrollment
.
course_id
),
"is_active"
:
enrollment
.
is_active
,
"name"
:
descriptor
.
display_name
}
response_data
.
append
(
course_data
)
return
Response
(
response_data
,
status
=
status
.
HTTP_200_OK
)
else
:
status_code
=
status
.
HTTP_404_NOT_FOUND
return
Response
(
response_data
,
status
=
status_code
)
return
Response
({},
status
=
status
.
HTTP_404_NOT_FOUND
)
enrollments
=
CourseEnrollment
.
enrollments_for_user
(
user
=
user
)
response_data
=
[]
for
enrollment
in
enrollments
:
descriptor
=
store
.
get_course
(
enrollment
.
course_id
)
course_data
=
{
"id"
:
enrollment
.
course_id
,
"uri"
:
'{}/{}'
.
format
(
base_uri
,
enrollment
.
course_id
),
"is_active"
:
enrollment
.
is_active
,
"name"
:
descriptor
.
display_name
}
response_data
.
append
(
course_data
)
return
Response
(
response_data
,
status
=
status
.
HTTP_200_OK
)
class
UsersCoursesDetail
(
SecureAPIView
):
""" Inherit with SecureAPIView """
"""
### The UsersCoursesDetail view allows clients to interact with a specific User-Course relationship (aka, enrollment)
- URI: ```/api/users/{user_id}/courses/{course_id}```
- POST: Stores the last-known location for the Course, for the specified User
* position: The parent-child identifier set for the Content being set as the last-known position, consisting of:
** parent_content_id, normally the Course identifier
** child_content_id, normally the Chapter identifier
- POST Example:
{
"position" : {
"parent_content_id" : "edX/Open_DemoX/edx_demo_course",
"child_content_id" : "i4x://edX/Open_DemoX/chapter/d8a6192ade314473a78242dfeedfbf5b"
}
}
- GET: Returns a JSON representation of the specified User-Course relationship
- DELETE: Inactivates (but does not remove) a Course relationship for the specified User
### Use Cases/Notes:
* Use the UsersCoursesDetail view to manage EXISTING Course enrollments
* Use GET to confirm that a User is actively enrolled in a particular course
* Use DELETE to unenroll a User from a Course (inactivates the enrollment)
* Use POST to record the last-known position within a Course (essentially, a bookmark)
* Note: To create a new Course enrollment, see UsersCoursesList
"""
def
post
(
self
,
request
,
user_id
,
course_id
):
"""
POST
creates an ACTIVE course enrollment for the specified user
POST
/api/users/{user_id}/courses/{course_id}
"""
store
=
modulestore
()
base_uri
=
_generate_base_uri
(
request
)
...
...
@@ -540,27 +651,22 @@ class UsersCoursesDetail(SecureAPIView):
user
=
User
.
objects
.
get
(
id
=
user_id
)
course_descriptor
=
store
.
get_course
(
course_id
)
except
(
ObjectDoesNotExist
,
ValueError
):
user
=
None
course_descriptor
=
None
if
user
and
course_descriptor
:
response_data
[
'user_id'
]
=
user
.
id
response_data
[
'course_id'
]
=
course_id
response_status
=
status
.
HTTP_201_CREATED
if
request
.
DATA
[
'position'
]:
response_data
[
'position'
]
=
_save_content_position
(
request
,
user
,
course_id
,
course_descriptor
,
request
.
DATA
[
'position'
]
)
else
:
response_status
=
status
.
HTTP_404_NOT_FOUND
return
Response
(
response_data
,
status
=
response_status
)
return
Response
(
response_data
,
status
=
status
.
HTTP_404_NOT_FOUND
)
response_data
[
'user_id'
]
=
user
.
id
response_data
[
'course_id'
]
=
course_id
if
request
.
DATA
[
'position'
]:
response_data
[
'position'
]
=
_save_content_position
(
request
,
user
,
course_id
,
course_descriptor
,
request
.
DATA
[
'position'
]
)
return
Response
(
response_data
,
status
=
status
.
HTTP_200_OK
)
def
get
(
self
,
request
,
user_id
,
course_id
):
"""
GET
identifies an ACTIVE course enrollment for the specified user
GET
/api/users/{user_id}/courses/{course_id}
"""
store
=
modulestore
()
response_data
=
{}
...
...
@@ -569,43 +675,47 @@ class UsersCoursesDetail(SecureAPIView):
user
=
User
.
objects
.
get
(
id
=
user_id
,
is_active
=
True
)
course_descriptor
=
store
.
get_course
(
course_id
)
except
(
ObjectDoesNotExist
,
ValueError
):
user
=
None
course_descriptor
=
None
if
user
and
CourseEnrollment
.
is_enrolled
(
user
,
course_id
):
response_data
[
'user_id'
]
=
user
.
id
response_data
[
'course_id'
]
=
course_id
response_data
[
'uri'
]
=
base_uri
field_data_cache
=
FieldDataCache
([
course_descriptor
],
course_id
,
user
)
course_content
=
module_render
.
get_module
(
user
,
request
,
course_descriptor
.
location
,
field_data_cache
,
course_id
)
response_data
[
'position'
]
=
course_content
.
position
response_status
=
status
.
HTTP_200_OK
else
:
response_status
=
status
.
HTTP_404_NOT_FOUND
return
Response
(
response_data
,
status
=
response_status
)
return
Response
({},
status
=
status
.
HTTP_404_NOT_FOUND
)
if
not
CourseEnrollment
.
is_enrolled
(
user
,
course_id
):
return
Response
({},
status
=
status
.
HTTP_404_NOT_FOUND
)
response_data
[
'user_id'
]
=
user
.
id
response_data
[
'course_id'
]
=
course_id
response_data
[
'uri'
]
=
base_uri
field_data_cache
=
FieldDataCache
([
course_descriptor
],
course_id
,
user
)
course_content
=
module_render
.
get_module
(
user
,
request
,
course_descriptor
.
location
,
field_data_cache
,
course_id
)
response_data
[
'position'
]
=
course_content
.
position
return
Response
(
response_data
,
status
=
status
.
HTTP_200_OK
)
def
delete
(
self
,
request
,
user_id
,
course_id
):
"""
DELETE
unenrolls the specified user from a course
DELETE
/api/users/{user_id}/courses/{course_id}
"""
try
:
user
=
User
.
objects
.
get
(
id
=
user_id
,
is_active
=
True
)
except
ObjectDoesNotExist
:
user
=
None
if
user
:
CourseEnrollment
.
unenroll
(
user
,
course_id
)
return
Response
({},
status
=
status
.
HTTP_204_NO_CONTENT
)
CourseEnrollment
.
unenroll
(
user
,
course_id
)
return
Response
({},
status
=
status
.
HTTP_204_NO_CONTENT
)
class
UsersCoursesGradesDetail
(
SecureAPIView
):
""" Inherit with SecureAPIView """
"""
### The UsersCoursesGradesDetail view allows clients to interact with the User's gradebook for a particular Course
- URI: ```/api/users/{user_id}/courses/{course_id}/grades```
- GET: Returns a JSON representation of the specified Course gradebook
### Use Cases/Notes:
* Use the UsersCoursesDetail view to manage the User's gradebook for a Course enrollment
* Use GET to retrieve the Course gradebook for the specified User
"""
def
get
(
self
,
request
,
user_id
,
course_id
):
"""
GET
returns the current gradebook for the user in a course
GET
/api/users/{user_id}/courses/{course_id}/grades
"""
# @TODO: Add authorization check here once we get caller identity
...
...
@@ -639,7 +749,22 @@ class UsersCoursesGradesDetail(SecureAPIView):
class
UsersPreferences
(
SecureAPIView
):
""" Inherit with SecureAPIView """
"""
### The UsersPreferences view allows clients to interact with the set of Preference key-value pairs related to the specified User
- URI: ```/api/users/{user_id}/preferences/```
- GET: Returns a JSON representation (dict) of the set of User preferences
- POST: Append a new UserPreference key-value pair to the set of preferences for the specified User
* "keyname": __required__, The identifier (aka, key) for the UserPreference being added. Values must be strings
- POST Example:
{
"favorite_color" : "blue"
}
### Use Cases/Notes:
* POSTing a non-string preference value will result in a 400 Bad Request response from the server
* POSTing a duplicate preference will cause the existing preference to be overwritten (effectively a PUT operation)
"""
def
get
(
self
,
request
,
user_id
):
# pylint: disable=W0613
"""
GET returns the preferences for the specified user
...
...
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