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
6574d611
Commit
6574d611
authored
Apr 14, 2016
by
Saqib
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add new fields to users list API and add organizaton, name and course enrolment filters
parent
b09b56c8
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
208 additions
and
16 deletions
+208
-16
lms/djangoapps/api_manager/users/serializers.py
+46
-3
lms/djangoapps/api_manager/users/tests.py
+130
-4
lms/djangoapps/api_manager/users/views.py
+32
-9
No files found.
lms/djangoapps/api_manager/users/serializers.py
View file @
6574d611
...
@@ -2,7 +2,7 @@
...
@@ -2,7 +2,7 @@
from
rest_framework
import
serializers
from
rest_framework
import
serializers
from
api_manager.models
import
APIUser
from
api_manager.models
import
APIUser
,
GroupProfile
from
organizations.serializers
import
BasicOrganizationSerializer
from
organizations.serializers
import
BasicOrganizationSerializer
from
student.models
import
UserProfile
from
student.models
import
UserProfile
...
@@ -27,6 +27,15 @@ class DynamicFieldsModelSerializer(serializers.ModelSerializer):
...
@@ -27,6 +27,15 @@ class DynamicFieldsModelSerializer(serializers.ModelSerializer):
self
.
fields
.
pop
(
field_name
)
self
.
fields
.
pop
(
field_name
)
class
GroupProfileSerializer
(
serializers
.
ModelSerializer
):
""" Serializer for GroupProfile model interactions """
class
Meta
(
object
):
""" Serializer/field specification """
model
=
GroupProfile
fields
=
(
'id'
,
'name'
,
)
class
UserSerializer
(
DynamicFieldsModelSerializer
):
class
UserSerializer
(
DynamicFieldsModelSerializer
):
""" Serializer for User model interactions """
""" Serializer for User model interactions """
...
@@ -37,11 +46,45 @@ class UserSerializer(DynamicFieldsModelSerializer):
...
@@ -37,11 +46,45 @@ class UserSerializer(DynamicFieldsModelSerializer):
title
=
serializers
.
CharField
(
source
=
'profile.title'
)
title
=
serializers
.
CharField
(
source
=
'profile.title'
)
country
=
serializers
.
CharField
(
source
=
'profile.country'
)
country
=
serializers
.
CharField
(
source
=
'profile.country'
)
full_name
=
serializers
.
CharField
(
source
=
'profile.name'
)
full_name
=
serializers
.
CharField
(
source
=
'profile.name'
)
courses_enrolled
=
serializers
.
SerializerMethodField
(
'get_courses_enrolled'
)
roles
=
serializers
.
SerializerMethodField
(
'get_permission_group_type_roles'
)
class
Meta
:
def
get_courses_enrolled
(
self
,
user
):
""" Serialize user enrolled courses """
if
hasattr
(
user
,
'courses_enrolled'
):
return
user
.
courses_enrolled
return
user
.
courseenrollment_set
.
count
def
get_permission_group_type_roles
(
self
,
user
):
""" Serialize GroupProfile for permission group type """
queryset
=
GroupProfile
.
objects
.
filter
(
group__user
=
user
,
group_type
=
'permission'
)
serializer
=
GroupProfileSerializer
(
queryset
,
many
=
True
)
return
serializer
.
data
class
Meta
(
object
):
""" Serializer/field specification """
""" Serializer/field specification """
model
=
APIUser
model
=
APIUser
fields
=
(
"id"
,
"email"
,
"username"
,
"first_name"
,
"last_name"
,
"created"
,
"is_active"
,
"organizations"
,
"avatar_url"
,
"city"
,
"title"
,
"country"
,
"full_name"
,
"is_staff"
)
fields
=
(
"id"
,
"email"
,
"username"
,
"first_name"
,
"last_name"
,
"created"
,
"is_active"
,
"organizations"
,
"avatar_url"
,
"city"
,
"title"
,
"country"
,
"full_name"
,
"is_staff"
,
"last_login"
,
"courses_enrolled"
,
"roles"
)
read_only_fields
=
(
"id"
,
"email"
,
"username"
)
read_only_fields
=
(
"id"
,
"email"
,
"username"
)
...
...
lms/djangoapps/api_manager/users/tests.py
View file @
6574d611
...
@@ -24,13 +24,15 @@ from django.test.utils import override_settings
...
@@ -24,13 +24,15 @@ from django.test.utils import override_settings
from
django.utils
import
timezone
from
django.utils
import
timezone
from
django.utils.translation
import
ugettext
as
_
from
django.utils.translation
import
ugettext
as
_
from
api_manager.models
import
GroupProfile
from
capa.tests.response_xml_factory
import
StringResponseXMLFactory
from
capa.tests.response_xml_factory
import
StringResponseXMLFactory
from
courseware
import
module_render
from
courseware
import
module_render
from
courseware.model_data
import
FieldDataCache
from
courseware.model_data
import
FieldDataCache
from
django_comment_common.models
import
Role
,
FORUM_ROLE_MODERATOR
from
django_comment_common.models
import
Role
,
FORUM_ROLE_MODERATOR
from
instructor.access
import
allow_access
from
instructor.access
import
allow_access
from
organizations.models
import
Organization
from
projects.models
import
Project
,
Workgroup
from
projects.models
import
Project
,
Workgroup
from
student.tests.factories
import
UserFactory
from
student.tests.factories
import
UserFactory
,
CourseEnrollmentFactory
,
GroupFactory
from
student.models
import
anonymous_id_for_user
from
student.models
import
anonymous_id_for_user
from
openedx.core.djangoapps.user_api.models
import
UserPreference
from
openedx.core.djangoapps.user_api.models
import
UserPreference
...
@@ -75,7 +77,6 @@ class SecureClient(Client):
...
@@ -75,7 +77,6 @@ class SecureClient(Client):
@override_settings
(
MODULESTORE
=
MODULESTORE_CONFIG
)
@override_settings
(
MODULESTORE
=
MODULESTORE_CONFIG
)
@override_settings
(
EDX_API_KEY
=
TEST_API_KEY
)
@override_settings
(
EDX_API_KEY
=
TEST_API_KEY
)
@override_settings
(
PASSWORD_MIN_LENGTH
=
4
)
@override_settings
(
PASSWORD_MIN_LENGTH
=
4
)
@override_settings
(
API_PAGE_SIZE
=
10
)
@mock.patch.dict
(
"django.conf.settings.FEATURES"
,
{
'ENFORCE_PASSWORD_POLICY'
:
True
})
@mock.patch.dict
(
"django.conf.settings.FEATURES"
,
{
'ENFORCE_PASSWORD_POLICY'
:
True
})
class
UsersApiTests
(
ModuleStoreTestCase
):
class
UsersApiTests
(
ModuleStoreTestCase
):
""" Test suite for Users API views """
""" Test suite for Users API views """
...
@@ -262,7 +263,14 @@ class UsersApiTests(ModuleStoreTestCase):
...
@@ -262,7 +263,14 @@ class UsersApiTests(ModuleStoreTestCase):
# fetch data without any filters applied
# fetch data without any filters applied
response
=
self
.
do_get
(
'{}?page=1'
.
format
(
test_uri
))
response
=
self
.
do_get
(
'{}?page=1'
.
format
(
test_uri
))
self
.
assertEqual
(
response
.
status_code
,
400
)
self
.
assertEqual
(
response
.
status_code
,
200
)
# test default page size
response
=
self
.
do_get
(
test_uri
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
len
(
response
.
data
[
'results'
]),
20
)
self
.
assertEqual
(
response
.
data
[
'num_pages'
],
2
)
# fetch users data with page outside range
# fetch users data with page outside range
response
=
self
.
do_get
(
'{}?ids={}&page=5'
.
format
(
test_uri
,
'2,3,7,11,6,21,34'
))
response
=
self
.
do_get
(
'{}?ids={}&page=5'
.
format
(
test_uri
,
'2,3,7,11,6,21,34'
))
self
.
assertEqual
(
response
.
status_code
,
404
)
self
.
assertEqual
(
response
.
status_code
,
404
)
...
@@ -274,6 +282,7 @@ class UsersApiTests(ModuleStoreTestCase):
...
@@ -274,6 +282,7 @@ class UsersApiTests(ModuleStoreTestCase):
self
.
assertIsNotNone
(
response
.
data
[
'results'
][
0
][
'organizations'
][
0
][
'name'
])
self
.
assertIsNotNone
(
response
.
data
[
'results'
][
0
][
'organizations'
][
0
][
'name'
])
self
.
assertIsNotNone
(
response
.
data
[
'results'
][
0
][
'organizations'
][
0
][
'id'
])
self
.
assertIsNotNone
(
response
.
data
[
'results'
][
0
][
'organizations'
][
0
][
'id'
])
self
.
assertIsNotNone
(
response
.
data
[
'results'
][
0
][
'organizations'
][
0
][
'url'
])
self
.
assertIsNotNone
(
response
.
data
[
'results'
][
0
][
'organizations'
][
0
][
'url'
])
self
.
assertIsNotNone
(
response
.
data
[
'results'
][
0
][
'last_login'
])
self
.
assertIsNotNone
(
response
.
data
[
'results'
][
0
][
'created'
])
self
.
assertIsNotNone
(
response
.
data
[
'results'
][
0
][
'created'
])
# fetch user data by multiple ids
# fetch user data by multiple ids
response
=
self
.
do_get
(
'{}?page_size=5&ids={}'
.
format
(
test_uri
,
'2,3,7,11,6,21,34'
))
response
=
self
.
do_get
(
'{}?page_size=5&ids={}'
.
format
(
test_uri
,
'2,3,7,11,6,21,34'
))
...
@@ -306,7 +315,57 @@ class UsersApiTests(ModuleStoreTestCase):
...
@@ -306,7 +315,57 @@ class UsersApiTests(ModuleStoreTestCase):
if
'id'
in
response
.
data
[
'results'
][
0
]:
if
'id'
in
response
.
data
[
'results'
][
0
]:
self
.
fail
(
"Dynamic field filtering error in UserSerializer"
)
self
.
fail
(
"Dynamic field filtering error in UserSerializer"
)
def
test_user_list_get_with_org_filter
(
self
):
def
test_user_list_get_courses_enrolled
(
self
):
test_uri
=
self
.
users_base_uri
# create a 2 new users
users
=
UserFactory
.
create_batch
(
2
)
# create course enrollments
CourseEnrollmentFactory
.
create
(
user
=
users
[
1
],
course_id
=
self
.
course
.
id
)
CourseEnrollmentFactory
.
create
(
user
=
users
[
1
],
course_id
=
self
.
course2
.
id
)
# fetch user 1
response
=
self
.
do_get
(
'{}?ids={}'
.
format
(
test_uri
,
users
[
0
]
.
id
))
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
len
(
response
.
data
[
'results'
]),
1
)
self
.
assertEqual
(
response
.
data
[
'results'
][
0
][
'courses_enrolled'
],
0
)
# fetch user 2
response
=
self
.
do_get
(
'{}?ids={}'
.
format
(
test_uri
,
users
[
1
]
.
id
))
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
len
(
response
.
data
[
'results'
]),
1
)
self
.
assertEqual
(
response
.
data
[
'results'
][
0
][
'courses_enrolled'
],
2
)
def
test_user_list_get_roles
(
self
):
test_uri
=
self
.
users_base_uri
# create a 3 new users
users
=
UserFactory
.
create_batch
(
3
)
groups
=
GroupFactory
.
create_batch
(
3
)
group_profile1
=
GroupProfile
.
objects
.
create
(
group
=
groups
[
0
],
name
=
'role1'
,
group_type
=
'permission'
)
group_profile2
=
GroupProfile
.
objects
.
create
(
group
=
groups
[
1
],
name
=
'role2'
,
group_type
=
'permission'
)
GroupProfile
.
objects
.
create
(
group
=
groups
[
2
],
name
=
'role3'
,
group_type
=
'test'
)
users
[
0
]
.
groups
.
add
(
*
groups
)
users
[
1
]
.
groups
.
add
(
groups
[
0
])
users
[
2
]
.
groups
.
add
(
groups
[
2
])
# fetch users
user_ids
=
','
.
join
([
str
(
user
.
id
)
for
user
in
users
])
response
=
self
.
do_get
(
'{}?ids={}'
.
format
(
test_uri
,
user_ids
))
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
len
(
response
.
data
[
'results'
]),
3
)
self
.
assertEqual
(
len
(
response
.
data
[
'results'
][
0
][
'roles'
]),
2
)
self
.
assertEqual
(
response
.
data
[
'results'
][
0
][
'roles'
][
0
][
'id'
],
group_profile1
.
id
)
self
.
assertEqual
(
response
.
data
[
'results'
][
0
][
'roles'
][
0
][
'name'
],
'role1'
)
self
.
assertEqual
(
response
.
data
[
'results'
][
0
][
'roles'
][
1
][
'id'
],
group_profile2
.
id
)
self
.
assertEqual
(
response
.
data
[
'results'
][
0
][
'roles'
][
1
][
'name'
],
'role2'
)
self
.
assertEqual
(
len
(
response
.
data
[
'results'
][
1
][
'roles'
]),
1
)
self
.
assertEqual
(
response
.
data
[
'results'
][
1
][
'roles'
][
0
][
'id'
],
group_profile1
.
id
)
self
.
assertEqual
(
response
.
data
[
'results'
][
1
][
'roles'
][
0
][
'name'
],
'role1'
)
self
.
assertEqual
(
len
(
response
.
data
[
'results'
][
2
][
'roles'
]),
0
)
def
test_user_list_get_with_has_organization_filter
(
self
):
test_uri
=
self
.
users_base_uri
test_uri
=
self
.
users_base_uri
users
=
[]
users
=
[]
# create a 7 new users
# create a 7 new users
...
@@ -344,6 +403,73 @@ class UsersApiTests(ModuleStoreTestCase):
...
@@ -344,6 +403,73 @@ class UsersApiTests(ModuleStoreTestCase):
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertGreaterEqual
(
len
(
response
.
data
[
'results'
]),
4
)
self
.
assertGreaterEqual
(
len
(
response
.
data
[
'results'
]),
4
)
def
test_user_list_get_with_organizations_filter
(
self
):
test_uri
=
self
.
users_base_uri
# create a 8 new users
users
=
UserFactory
.
create_batch
(
8
)
# create organization and add 4 users to it
organizations
=
[]
for
i
in
xrange
(
2
):
organization
=
Organization
.
objects
.
create
(
name
=
'Test Organization{}'
.
format
(
i
),
display_name
=
'Test Org Display Name{}'
.
format
(
i
),
)
organizations
.
append
(
organization
)
organizations
[
0
]
.
users
.
add
(
*
users
)
organizations
[
1
]
.
users
.
add
(
*
users
[:
4
])
# fetch users for organization 1
response
=
self
.
do_get
(
'{}?organizations={}'
.
format
(
test_uri
,
organizations
[
1
]
.
id
))
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
len
(
response
.
data
[
'results'
]),
4
)
self
.
assertIsNotNone
(
response
.
data
[
'results'
][
0
][
'is_active'
])
# fetch users in multiple organization
organization_ids
=
','
.
join
([
str
(
organization
.
id
)
for
organization
in
organizations
])
response
=
self
.
do_get
(
'{}?organizations={}'
.
format
(
test_uri
,
organization_ids
))
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
len
(
response
.
data
[
'results'
]),
8
)
self
.
assertIsNotNone
(
response
.
data
[
'results'
][
0
][
'is_active'
])
def
test_user_list_get_with_course_enrollment_filter
(
self
):
test_uri
=
self
.
users_base_uri
# create a 8 new users
users
=
UserFactory
.
create_batch
(
8
)
# create course enrollments
for
user
in
users
[:
4
]:
CourseEnrollmentFactory
.
create
(
user
=
user
,
course_id
=
self
.
course
.
id
)
for
user
in
users
:
CourseEnrollmentFactory
.
create
(
user
=
user
,
course_id
=
self
.
course2
.
id
)
# fetch users enrolled in course 1
response
=
self
.
do_get
(
'{}?courses={}'
.
format
(
test_uri
,
self
.
course
.
id
))
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
len
(
response
.
data
[
'results'
]),
4
)
self
.
assertIsNotNone
(
response
.
data
[
'results'
][
0
][
'is_active'
])
# fetch users enrolled in course 1 and 2
response
=
self
.
do_get
(
'{}?courses={},{}'
.
format
(
test_uri
,
self
.
course
.
id
,
self
.
course2
.
id
))
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
len
(
response
.
data
[
'results'
]),
8
)
self
.
assertIsNotNone
(
response
.
data
[
'results'
][
0
][
'is_active'
])
def
test_user_list_get_with_name_filter
(
self
):
test_uri
=
self
.
users_base_uri
# create a 8 new users
users
=
UserFactory
.
create_batch
(
2
)
users
.
append
(
UserFactory
.
create_batch
(
2
,
first_name
=
"John"
,
last_name
=
"Doe"
))
# fetch users by name
response
=
self
.
do_get
(
'{}?name=John Doe'
.
format
(
test_uri
))
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
len
(
response
.
data
[
'results'
]),
2
)
self
.
assertEqual
(
response
.
data
[
'results'
][
0
][
'first_name'
],
'John'
)
self
.
assertEqual
(
response
.
data
[
'results'
][
0
][
'last_name'
],
'Doe'
)
def
test_user_list_post
(
self
):
def
test_user_list_post
(
self
):
test_uri
=
self
.
users_base_uri
test_uri
=
self
.
users_base_uri
local_username
=
self
.
test_username
+
str
(
randint
(
11
,
99
))
local_username
=
self
.
test_username
+
str
(
randint
(
11
,
99
))
...
...
lms/djangoapps/api_manager/users/views.py
View file @
6574d611
...
@@ -209,14 +209,19 @@ class UsersList(SecureListAPIView):
...
@@ -209,14 +209,19 @@ class UsersList(SecureListAPIView):
"""
"""
### The UsersList view allows clients to retrieve/append a list of User entities
### The UsersList view allows clients to retrieve/append a list of User entities
- URI: ```/api/users/```
- URI: ```/api/users/```
- GET: Provides paginated list of users, it supports email, username, has_organizations and id filters
- GET: Provides paginated list of users, it supports email, username, name, organizations, courses enrolled,
has_organizations and id filters
Possible use cases
Possible use cases
GET /api/users?ids=23
GET /api/users?ids=23
GET /api/users?ids=11,12,13&page=2
GET /api/users?ids=11,12,13&page=2
GET /api/users?organizations=1,2,3
GET /api/users?courses={course_id},{course_id2}
GET /api/users?email={john@example.com}
GET /api/users?email={john@example.com}
GET /api/users?name={john doe}
GET /api/users?username={john}
GET /api/users?username={john}
* email: string, filters user set by email address
* email: string, filters user set by email address
* username: string, filters user set by username
* username: string, filters user set by username
* name: string, filters user set by full name
GET /api/users?has_organizations={true}
GET /api/users?has_organizations={true}
* has_organizations: boolean, filters user set with organization association
* has_organizations: boolean, filters user set with organization association
GET /api/users?has_organizations={false}
GET /api/users?has_organizations={false}
...
@@ -269,18 +274,36 @@ class UsersList(SecureListAPIView):
...
@@ -269,18 +274,36 @@ class UsersList(SecureListAPIView):
filter_backends
=
(
filters
.
DjangoFilterBackend
,
IdsInFilterBackend
,
HasOrgsFilterBackend
)
filter_backends
=
(
filters
.
DjangoFilterBackend
,
IdsInFilterBackend
,
HasOrgsFilterBackend
)
filter_fields
=
(
'email'
,
'username'
,
)
filter_fields
=
(
'email'
,
'username'
,
)
def
get_queryset
(
self
):
"""
Optionally filter users by organizations and course enrollments
"""
queryset
=
self
.
queryset
org_ids
=
self
.
request
.
QUERY_PARAMS
.
get
(
'organizations'
,
None
)
if
org_ids
is
not
None
:
org_ids
=
map
(
int
,
org_ids
.
split
(
','
))
queryset
=
queryset
.
filter
(
organizations__id__in
=
org_ids
)
.
distinct
()
course_ids
=
self
.
request
.
QUERY_PARAMS
.
get
(
'courses'
,
None
)
if
course_ids
is
not
None
:
course_ids
=
map
(
CourseKey
.
from_string
,
course_ids
.
split
(
','
))
queryset
=
queryset
.
filter
(
courseenrollment__course_id__in
=
course_ids
)
.
distinct
()
name
=
self
.
request
.
QUERY_PARAMS
.
get
(
'name'
,
None
)
if
name
is
not
None
:
queryset
=
queryset
.
filter
(
profile__name
=
name
)
queryset
=
queryset
.
prefetch_related
(
'organizations'
)
\
.
select_related
(
'courseenrollment_set'
,
'profile'
)
\
.
annotate
(
courses_enrolled
=
Count
(
'courseenrollment'
))
return
queryset
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
"""
"""
GET /api/users?ids=11,12,13.....&page=2
GET /api/users?ids=11,12,13.....&page=2
"""
"""
email
=
request
.
QUERY_PARAMS
.
get
(
'email'
,
None
)
return
self
.
list
(
request
,
*
args
,
**
kwargs
)
username
=
request
.
QUERY_PARAMS
.
get
(
'username'
,
None
)
ids
=
request
.
QUERY_PARAMS
.
get
(
'ids'
,
None
)
has_orgs
=
request
.
QUERY_PARAMS
.
get
(
'has_organizations'
,
None
)
if
email
or
username
or
ids
or
has_orgs
:
return
self
.
list
(
request
,
*
args
,
**
kwargs
)
else
:
return
Response
({
'message'
:
_
(
'Unfiltered request is not allowed.'
)},
status
=
status
.
HTTP_400_BAD_REQUEST
)
def
post
(
self
,
request
):
def
post
(
self
,
request
):
"""
"""
...
...
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