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
4bbe9a20
Commit
4bbe9a20
authored
Feb 19, 2016
by
Nimisha Asthagiri
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #11326 from edx/mobile/remove-dead-code
Mobile API: remove unused endpoints
parents
8f7c158f
c088df08
Hide whitespace changes
Inline
Side-by-side
Showing
43 changed files
with
18 additions
and
2244 deletions
+18
-2244
cms/djangoapps/models/settings/course_metadata.py
+0
-4
common/lib/xmodule/xmodule/course_module.py
+0
-9
lms/djangoapps/course_structure_api/v0/tests.py
+0
-233
lms/djangoapps/course_structure_api/v0/urls.py
+0
-24
lms/djangoapps/course_structure_api/v0/views.py
+0
-400
lms/djangoapps/mobile_api/social_facebook/__init__.py
+0
-42
lms/djangoapps/mobile_api/social_facebook/courses/__init__.py
+0
-3
lms/djangoapps/mobile_api/social_facebook/courses/models.py
+0
-3
lms/djangoapps/mobile_api/social_facebook/courses/serializers.py
+0
-11
lms/djangoapps/mobile_api/social_facebook/courses/tests.py
+0
-147
lms/djangoapps/mobile_api/social_facebook/courses/urls.py
+0
-15
lms/djangoapps/mobile_api/social_facebook/courses/views.py
+0
-65
lms/djangoapps/mobile_api/social_facebook/friends/__init__.py
+0
-3
lms/djangoapps/mobile_api/social_facebook/friends/models.py
+0
-3
lms/djangoapps/mobile_api/social_facebook/friends/serializers.py
+0
-11
lms/djangoapps/mobile_api/social_facebook/friends/tests.py
+0
-318
lms/djangoapps/mobile_api/social_facebook/friends/urls.py
+0
-16
lms/djangoapps/mobile_api/social_facebook/friends/views.py
+0
-71
lms/djangoapps/mobile_api/social_facebook/groups/__init__.py
+0
-3
lms/djangoapps/mobile_api/social_facebook/groups/models.py
+0
-3
lms/djangoapps/mobile_api/social_facebook/groups/serializers.py
+0
-30
lms/djangoapps/mobile_api/social_facebook/groups/tests.py
+0
-199
lms/djangoapps/mobile_api/social_facebook/groups/urls.py
+0
-20
lms/djangoapps/mobile_api/social_facebook/groups/views.py
+0
-143
lms/djangoapps/mobile_api/social_facebook/models.py
+0
-3
lms/djangoapps/mobile_api/social_facebook/preferences/__init__.py
+0
-3
lms/djangoapps/mobile_api/social_facebook/preferences/models.py
+0
-3
lms/djangoapps/mobile_api/social_facebook/preferences/serializers.py
+0
-11
lms/djangoapps/mobile_api/social_facebook/preferences/tests.py
+0
-67
lms/djangoapps/mobile_api/social_facebook/preferences/urls.py
+0
-14
lms/djangoapps/mobile_api/social_facebook/preferences/views.py
+0
-52
lms/djangoapps/mobile_api/social_facebook/test_utils.py
+0
-186
lms/djangoapps/mobile_api/social_facebook/urls.py
+0
-11
lms/djangoapps/mobile_api/social_facebook/utils.py
+0
-76
lms/djangoapps/mobile_api/urls.py
+0
-7
lms/djangoapps/mobile_api/users/serializers.py
+0
-8
lms/djangoapps/mobile_api/users/tests.py
+0
-17
lms/djangoapps/mobile_api/users/views.py
+0
-1
lms/envs/common.py
+0
-4
lms/envs/test.py
+0
-2
openedx/core/djangoapps/content/course_overviews/migrations/0008_remove_courseoverview_facebook_url.py
+18
-0
openedx/core/djangoapps/content/course_overviews/models.py
+0
-2
openedx/core/djangoapps/content/course_overviews/tests.py
+0
-1
No files found.
cms/djangoapps/models/settings/course_metadata.py
View file @
4bbe9a20
...
@@ -73,10 +73,6 @@ class CourseMetadata(object):
...
@@ -73,10 +73,6 @@ class CourseMetadata(object):
if
not
settings
.
FEATURES
.
get
(
'ENABLE_VIDEO_UPLOAD_PIPELINE'
):
if
not
settings
.
FEATURES
.
get
(
'ENABLE_VIDEO_UPLOAD_PIPELINE'
):
filtered_list
.
append
(
'video_upload_pipeline'
)
filtered_list
.
append
(
'video_upload_pipeline'
)
# Do not show facebook_url if the feature is disabled.
if
not
settings
.
FEATURES
.
get
(
'ENABLE_MOBILE_SOCIAL_FACEBOOK_FEATURES'
):
filtered_list
.
append
(
'facebook_url'
)
# Do not show social sharing url field if the feature is disabled.
# Do not show social sharing url field if the feature is disabled.
if
(
not
hasattr
(
settings
,
'SOCIAL_SHARING_SETTINGS'
)
or
if
(
not
hasattr
(
settings
,
'SOCIAL_SHARING_SETTINGS'
)
or
not
getattr
(
settings
,
'SOCIAL_SHARING_SETTINGS'
,
{})
.
get
(
"CUSTOM_COURSE_URLS"
)):
not
getattr
(
settings
,
'SOCIAL_SHARING_SETTINGS'
,
{})
.
get
(
"CUSTOM_COURSE_URLS"
)):
...
...
common/lib/xmodule/xmodule/course_module.py
View file @
4bbe9a20
...
@@ -338,15 +338,6 @@ class CourseFields(object):
...
@@ -338,15 +338,6 @@ class CourseFields(object):
help
=
_
(
"Enter the unique identifier for your course's video files provided by edX."
),
help
=
_
(
"Enter the unique identifier for your course's video files provided by edX."
),
scope
=
Scope
.
settings
scope
=
Scope
.
settings
)
)
facebook_url
=
String
(
help
=
_
(
"Enter the URL for the official course Facebook group. "
"If you provide a URL, the mobile app includes a button that students can tap to access the group."
),
default
=
None
,
display_name
=
_
(
"Facebook URL"
),
scope
=
Scope
.
settings
)
no_grade
=
Boolean
(
no_grade
=
Boolean
(
display_name
=
_
(
"Course Not Graded"
),
display_name
=
_
(
"Course Not Graded"
),
help
=
_
(
"Enter true or false. If true, the course will not be graded."
),
help
=
_
(
"Enter true or false. If true, the course will not be graded."
),
...
...
lms/djangoapps/course_structure_api/v0/tests.py
View file @
4bbe9a20
...
@@ -3,10 +3,8 @@ Run these tests @ Devstack:
...
@@ -3,10 +3,8 @@ Run these tests @ Devstack:
paver test_system -s lms --fasttest --verbose --test_id=lms/djangoapps/course_structure_api
paver test_system -s lms --fasttest --verbose --test_id=lms/djangoapps/course_structure_api
"""
"""
# pylint: disable=missing-docstring,invalid-name,maybe-no-member,attribute-defined-outside-init
# pylint: disable=missing-docstring,invalid-name,maybe-no-member,attribute-defined-outside-init
from
abc
import
ABCMeta
from
datetime
import
datetime
from
datetime
import
datetime
from
mock
import
patch
,
Mock
from
mock
import
patch
,
Mock
from
itertools
import
product
from
django.core.urlresolvers
import
reverse
from
django.core.urlresolvers
import
reverse
...
@@ -15,13 +13,11 @@ from oauth2_provider.tests.factories import AccessTokenFactory, ClientFactory
...
@@ -15,13 +13,11 @@ from oauth2_provider.tests.factories import AccessTokenFactory, ClientFactory
from
opaque_keys.edx.locator
import
CourseLocator
from
opaque_keys.edx.locator
import
CourseLocator
from
xmodule.error_module
import
ErrorDescriptor
from
xmodule.error_module
import
ErrorDescriptor
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
SharedModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
,
check_mongo_calls
from
xmodule.modulestore.tests.factories
import
CourseFactory
,
ItemFactory
,
check_mongo_calls
from
xmodule.modulestore.xml
import
CourseLocationManager
from
xmodule.modulestore.xml
import
CourseLocationManager
from
xmodule.tests
import
get_test_system
from
xmodule.tests
import
get_test_system
from
student.tests.factories
import
UserFactory
,
CourseEnrollmentFactory
from
courseware.tests.factories
import
GlobalStaffFactory
,
StaffFactory
from
courseware.tests.factories
import
GlobalStaffFactory
,
StaffFactory
from
openedx.core.djangoapps.content.course_structures.models
import
CourseStructure
from
openedx.core.djangoapps.content.course_structures.models
import
CourseStructure
from
openedx.core.djangoapps.content.course_structures.tasks
import
update_course_structure
from
openedx.core.djangoapps.content.course_structures.tasks
import
update_course_structure
...
@@ -457,232 +453,3 @@ class CourseGradingPolicyMissingFieldsTests(CourseDetailTestMixin, CourseViewTes
...
@@ -457,232 +453,3 @@ class CourseGradingPolicyMissingFieldsTests(CourseDetailTestMixin, CourseViewTes
}
}
]
]
self
.
assertListEqual
(
response
.
data
,
expected
)
self
.
assertListEqual
(
response
.
data
,
expected
)
#####################################################################################
#
# The following Mixins/Classes collectively test the CourseBlocksAndNavigation view.
#
# The class hierarchy is:
#
# -----------------> CourseBlocksOrNavigationTestMixin <--------------
# | ^ |
# | | |
# | CourseNavigationTestMixin | CourseBlocksTestMixin |
# | ^ ^ | ^ ^ |
# | | | | | | |
# | | | | | | |
# CourseNavigationTests CourseBlocksAndNavigationTests CourseBlocksTests
#
#
# Each Test Mixin is an abstract class that implements tests specific to its
# corresponding functionality.
#
# The concrete Test classes are expected to define the following class fields:
#
# block_navigation_view_type - The view's name as it should be passed to the django
# reverse method.
# container_fields - A list of fields that are expected to be included in the view's
# response for all container block types.
# block_fields - A list of fields that are expected to be included in the view's
# response for all block types.
#
######################################################################################
class
CourseBlocksOrNavigationTestMixin
(
CourseDetailTestMixin
,
CourseViewTestsMixin
):
"""
A Mixin class for testing all views related to Course blocks and/or navigation.
"""
__metaclass__
=
ABCMeta
view_supports_debug_mode
=
False
def
setUp
(
self
):
"""
Override the base `setUp` method to enroll the user in the course, since these views
require enrollment for non-staff users.
"""
super
(
CourseBlocksOrNavigationTestMixin
,
self
)
.
setUp
()
CourseEnrollmentFactory
(
user
=
self
.
user
,
course_id
=
self
.
course
.
id
)
def
create_user
(
self
):
"""
Override the base `create_user` method to test with non-staff users for these views.
"""
self
.
user
=
UserFactory
.
create
()
@property
def
view
(
self
):
"""
Returns the name of the view for testing to use in the django `reverse` call.
"""
return
'course_structure_api:v0:'
+
self
.
block_navigation_view_type
def
test_get
(
self
):
with
check_mongo_calls
(
4
):
response
=
super
(
CourseBlocksOrNavigationTestMixin
,
self
)
.
test_get
()
# verify root element
self
.
assertIn
(
'root'
,
response
.
data
)
root_string
=
unicode
(
self
.
course
.
location
)
self
.
assertEquals
(
response
.
data
[
'root'
],
root_string
)
# verify ~blocks element
self
.
assertTrue
(
self
.
block_navigation_view_type
in
response
.
data
)
blocks
=
response
.
data
[
self
.
block_navigation_view_type
]
# verify number of blocks
self
.
assertEquals
(
len
(
blocks
),
5
)
# verify fields in blocks
for
field
,
block
in
product
(
self
.
block_fields
,
blocks
.
values
()):
self
.
assertIn
(
field
,
block
)
# verify container fields in container blocks
for
field
in
self
.
container_fields
:
self
.
assertIn
(
field
,
blocks
[
root_string
])
def
test_parse_error
(
self
):
"""
Verifies the view returns a 400 when a query parameter is incorrectly formatted.
"""
response
=
self
.
http_get_for_course
(
data
=
{
'block_json'
:
'incorrect'
})
self
.
assertEqual
(
response
.
status_code
,
400
)
@SharedModuleStoreTestCase.modifies_courseware
def
test_no_access_to_block
(
self
):
"""
Verifies the view returns only the top-level course block, excluding the sequential block
and its descendants when the user does not have access to the sequential.
"""
self
.
sequential
.
visible_to_staff_only
=
True
modulestore
()
.
update_item
(
self
.
sequential
,
self
.
user
.
id
)
response
=
super
(
CourseBlocksOrNavigationTestMixin
,
self
)
.
test_get
()
self
.
assertEquals
(
len
(
response
.
data
[
self
.
block_navigation_view_type
]),
1
)
class
CourseBlocksTestMixin
(
object
):
"""
A Mixin class for testing all views related to Course blocks.
"""
__metaclass__
=
ABCMeta
view_supports_debug_mode
=
False
block_fields
=
[
'id'
,
'type'
,
'display_name'
,
'web_url'
,
'block_url'
,
'graded'
,
'format'
]
def
test_block_json
(
self
):
"""
Verifies the view's response when the block_json data is requested.
"""
response
=
self
.
http_get_for_course
(
data
=
{
'block_json'
:
'{"video":{"profiles":["mobile_low"]}}'
}
)
self
.
assertEquals
(
response
.
status_code
,
200
)
video_block
=
response
.
data
[
self
.
block_navigation_view_type
][
unicode
(
self
.
video
.
location
)]
self
.
assertIn
(
'block_json'
,
video_block
)
def
test_block_count
(
self
):
"""
Verifies the view's response when the block_count data is requested.
"""
response
=
self
.
http_get_for_course
(
data
=
{
'block_count'
:
'problem'
}
)
self
.
assertEquals
(
response
.
status_code
,
200
)
root_block
=
response
.
data
[
self
.
block_navigation_view_type
][
unicode
(
self
.
course
.
location
)]
self
.
assertIn
(
'block_count'
,
root_block
)
self
.
assertIn
(
'problem'
,
root_block
[
'block_count'
])
self
.
assertEquals
(
root_block
[
'block_count'
][
'problem'
],
1
)
def
test_multi_device_support
(
self
):
"""
Verifies the view's response when multi_device support is requested.
"""
response
=
self
.
http_get_for_course
(
data
=
{
'fields'
:
'multi_device'
}
)
self
.
assertEquals
(
response
.
status_code
,
200
)
for
block
,
expected_multi_device_support
in
(
(
self
.
problem
,
True
),
(
self
.
html
,
True
),
(
self
.
video
,
False
)
):
block_response
=
response
.
data
[
self
.
block_navigation_view_type
][
unicode
(
block
.
location
)]
self
.
assertEquals
(
block_response
[
'multi_device'
],
expected_multi_device_support
)
class
CourseNavigationTestMixin
(
object
):
"""
A Mixin class for testing all views related to Course navigation.
"""
__metaclass__
=
ABCMeta
def
test_depth_zero
(
self
):
"""
Tests that all descendants are bundled into the root block when the navigation_depth is set to 0.
"""
response
=
self
.
http_get_for_course
(
data
=
{
'navigation_depth'
:
'0'
}
)
root_block
=
response
.
data
[
self
.
block_navigation_view_type
][
unicode
(
self
.
course
.
location
)]
self
.
assertIn
(
'descendants'
,
root_block
)
self
.
assertEquals
(
len
(
root_block
[
'descendants'
]),
4
)
def
test_depth
(
self
):
"""
Tests that all container blocks have descendants listed in their data.
"""
response
=
self
.
http_get_for_course
()
container_descendants
=
(
(
self
.
course
.
location
,
1
),
(
self
.
sequential
.
location
,
3
),
)
for
container_location
,
expected_num_descendants
in
container_descendants
:
block
=
response
.
data
[
self
.
block_navigation_view_type
][
unicode
(
container_location
)]
self
.
assertIn
(
'descendants'
,
block
)
self
.
assertEquals
(
len
(
block
[
'descendants'
]),
expected_num_descendants
)
class
CourseBlocksTests
(
CourseBlocksOrNavigationTestMixin
,
CourseBlocksTestMixin
,
SharedModuleStoreTestCase
):
"""
A Test class for testing the Course 'blocks' view.
"""
block_navigation_view_type
=
'blocks'
container_fields
=
[
'children'
]
@classmethod
def
setUpClass
(
cls
):
super
(
CourseBlocksTests
,
cls
)
.
setUpClass
()
cls
.
create_course_data
()
class
CourseNavigationTests
(
CourseBlocksOrNavigationTestMixin
,
CourseNavigationTestMixin
,
SharedModuleStoreTestCase
):
"""
A Test class for testing the Course 'navigation' view.
"""
block_navigation_view_type
=
'navigation'
container_fields
=
[
'descendants'
]
block_fields
=
[]
@classmethod
def
setUpClass
(
cls
):
super
(
CourseNavigationTests
,
cls
)
.
setUpClass
()
cls
.
create_course_data
()
class
CourseBlocksAndNavigationTests
(
CourseBlocksOrNavigationTestMixin
,
CourseBlocksTestMixin
,
CourseNavigationTestMixin
,
SharedModuleStoreTestCase
):
"""
A Test class for testing the Course 'blocks+navigation' view.
"""
block_navigation_view_type
=
'blocks+navigation'
container_fields
=
[
'children'
,
'descendants'
]
@classmethod
def
setUpClass
(
cls
):
super
(
CourseBlocksAndNavigationTests
,
cls
)
.
setUpClass
()
cls
.
create_course_data
()
lms/djangoapps/course_structure_api/v0/urls.py
View file @
4bbe9a20
...
@@ -20,27 +20,3 @@ urlpatterns = patterns(
...
@@ -20,27 +20,3 @@ urlpatterns = patterns(
name
=
'grading_policy'
name
=
'grading_policy'
),
),
)
)
if
settings
.
FEATURES
.
get
(
'ENABLE_COURSE_BLOCKS_NAVIGATION_API'
):
# TODO (MA-789) This endpoint still needs to be approved by the arch council.
# TODO (MA-704) This endpoint still needs to be made performant.
urlpatterns
+=
(
url
(
r'^courses/{}/blocks/$'
.
format
(
COURSE_ID_PATTERN
),
views
.
CourseBlocksAndNavigation
.
as_view
(),
{
'return_blocks'
:
True
,
'return_nav'
:
False
},
name
=
'blocks'
),
url
(
r'^courses/{}/navigation/$'
.
format
(
COURSE_ID_PATTERN
),
views
.
CourseBlocksAndNavigation
.
as_view
(),
{
'return_blocks'
:
False
,
'return_nav'
:
True
},
name
=
'navigation'
),
url
(
r'^courses/{}/blocks\+navigation/$'
.
format
(
COURSE_ID_PATTERN
),
views
.
CourseBlocksAndNavigation
.
as_view
(),
{
'return_blocks'
:
True
,
'return_nav'
:
True
},
name
=
'blocks+navigation'
),
)
lms/djangoapps/course_structure_api/v0/views.py
View file @
4bbe9a20
""" API implementation for course-oriented interactions. """
""" API implementation for course-oriented interactions. """
from
collections
import
namedtuple
import
json
import
logging
import
logging
from
django.conf
import
settings
from
django.conf
import
settings
...
@@ -12,20 +10,15 @@ from rest_framework.exceptions import AuthenticationFailed, ParseError
...
@@ -12,20 +10,15 @@ from rest_framework.exceptions import AuthenticationFailed, ParseError
from
rest_framework.generics
import
RetrieveAPIView
,
ListAPIView
from
rest_framework.generics
import
RetrieveAPIView
,
ListAPIView
from
rest_framework.permissions
import
IsAuthenticated
from
rest_framework.permissions
import
IsAuthenticated
from
rest_framework.response
import
Response
from
rest_framework.response
import
Response
from
rest_framework.reverse
import
reverse
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
from
course_structure_api.v0
import
serializers
from
course_structure_api.v0
import
serializers
from
courseware
import
courses
from
courseware
import
courses
from
courseware.access
import
has_access
from
courseware.access
import
has_access
from
courseware.model_data
import
FieldDataCache
from
courseware.module_render
import
get_module_for_descriptor
from
openedx.core.lib.api.view_utils
import
view_course_access
,
view_auth_classes
from
openedx.core.djangoapps.content.course_structures.api.v0
import
api
,
errors
from
openedx.core.djangoapps.content.course_structures.api.v0
import
api
,
errors
from
openedx.core.lib.exceptions
import
CourseNotFoundError
from
openedx.core.lib.exceptions
import
CourseNotFoundError
from
student.roles
import
CourseInstructorRole
,
CourseStaffRole
from
student.roles
import
CourseInstructorRole
,
CourseStaffRole
from
util.module_utils
import
get_dynamic_descriptor_children
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
...
@@ -297,396 +290,3 @@ class CourseGradingPolicy(CourseViewMixin, ListAPIView):
...
@@ -297,396 +290,3 @@ class CourseGradingPolicy(CourseViewMixin, ListAPIView):
@CourseViewMixin.course_check
@CourseViewMixin.course_check
def
get
(
self
,
request
,
**
kwargs
):
def
get
(
self
,
request
,
**
kwargs
):
return
Response
(
api
.
course_grading_policy
(
self
.
course_key
))
return
Response
(
api
.
course_grading_policy
(
self
.
course_key
))
@view_auth_classes
()
class
CourseBlocksAndNavigation
(
ListAPIView
):
"""
**Use Case**
The following endpoints return the content of the course according to the requesting user's access level.
* Blocks - Get the course's blocks.
* Navigation - Get the course's navigation information per the navigation depth requested.
* Blocks+Navigation - Get both the course's blocks and the course's navigation information.
**Example requests**:
GET api/course_structure/v0/courses/{course_id}/blocks/
GET api/course_structure/v0/courses/{course_id}/navigation/
GET api/course_structure/v0/courses/{course_id}/blocks+navigation/
&block_count=video
&block_json={"video":{"profiles":["mobile_low"]}}
&fields=graded,format,multi_device
**Parameters**:
* block_json: (dict) Indicates for which block types to return student_view_json data. The key is the block
type and the value is the "context" that is passed to the block's student_view_json method.
Example: block_json={"video":{"profiles":["mobile_high","mobile_low"]}}
* block_count: (list) Indicates for which block types to return the aggregate count of the blocks.
Example: block_count="video,problem"
* fields: (list) Indicates which additional fields to return for each block.
Default is "children,graded,format,multi_device"
Example: fields=graded,format,multi_device
* navigation_depth (integer) Indicates how far deep to traverse into the course hierarchy before bundling
all the descendants.
Default is 3 since typical navigational views of the course show a maximum of chapter->sequential->vertical.
Example: navigation_depth=3
**Response Values**
The following fields are returned with a successful response.
Only either one of blocks, navigation, or blocks+navigation is returned depending on which endpoint is used.
The "root" field is returned for all endpoints.
* root: The ID of the root node of the course blocks.
* blocks: A dictionary that maps block usage IDs to a collection of information about each block.
Each block contains the following fields. Returned only if using the "blocks" endpoint.
* id: (string) The usage ID of the block.
* type: (string) The type of block. Possible values include course, chapter, sequential, vertical, html,
problem, video, and discussion. The type can also be the name of a custom type of block used for the course.
* display_name: (string) The display name of the block.
* children: (list) If the block has child blocks, a list of IDs of the child blocks.
Returned only if the "children" input parameter is True.
* block_count: (dict) For each block type specified in the block_count parameter to the endpoint, the
aggregate number of blocks of that type for this block and all of its descendants.
Returned only if the "block_count" input parameter contains this block's type.
* block_json: (dict) The JSON data for this block.
Returned only if the "block_json" input parameter contains this block's type.
* block_url: (string) The URL to retrieve the HTML rendering of this block. The HTML could include
CSS and Javascript code. This URL can be used as a fallback if the custom block_json for this
block type is not requested and not supported.
* web_url: (string) The URL to the website location of this block. This URL can be used as a further
fallback if the block_url and the block_json is not supported.
* graded (boolean) Whether or not the block or any of its descendants is graded.
Returned only if "graded" is included in the "fields" parameter.
* format: (string) The assignment type of the block.
Possible values can be "Homework", "Lab", "Midterm Exam", and "Final Exam".
Returned only if "format" is included in the "fields" parameter.
* multi_device: (boolean) Whether or not the block's rendering obtained via block_url has support
for multiple devices.
Returned only if "multi_device" is included in the "fields" parameter.
* navigation: A dictionary that maps block IDs to a collection of navigation information about each block.
Each block contains the following fields. Returned only if using the "navigation" endpoint.
* descendants: (list) A list of IDs of the children of the block if the block's depth in the
course hierarchy is less than the navigation_depth. Otherwise, a list of IDs of the aggregate descendants
of the block.
* blocks+navigation: A dictionary that combines both the blocks and navigation data.
Returned only if using the "blocks+navigation" endpoint.
"""
class
RequestInfo
(
object
):
"""
A class for encapsulating the request information, including what optional fields are requested.
"""
DEFAULT_FIELDS
=
"children,graded,format,multi_device"
def
__init__
(
self
,
request
,
course
):
self
.
request
=
request
self
.
course
=
course
self
.
field_data_cache
=
None
# check what fields are requested
try
:
# fields
self
.
fields
=
set
(
request
.
GET
.
get
(
'fields'
,
self
.
DEFAULT_FIELDS
)
.
split
(
","
))
# block_count
self
.
block_count
=
request
.
GET
.
get
(
'block_count'
,
""
)
self
.
block_count
=
(
self
.
block_count
.
split
(
","
)
if
self
.
block_count
else
[]
)
# navigation_depth
# See docstring for why we default to 3.
self
.
navigation_depth
=
int
(
request
.
GET
.
get
(
'navigation_depth'
,
'3'
))
# block_json
self
.
block_json
=
json
.
loads
(
request
.
GET
.
get
(
'block_json'
,
"{}"
))
if
self
.
block_json
and
not
isinstance
(
self
.
block_json
,
dict
):
raise
ParseError
except
:
raise
ParseError
class
ResultData
(
object
):
"""
A class for encapsulating the result information, specifically the blocks and navigation data.
"""
def
__init__
(
self
,
return_blocks
,
return_nav
):
self
.
blocks
=
{}
self
.
navigation
=
{}
if
return_blocks
and
return_nav
:
self
.
navigation
=
self
.
blocks
def
update_response
(
self
,
response
,
return_blocks
,
return_nav
):
"""
Updates the response object with result information.
"""
if
return_blocks
and
return_nav
:
response
[
"blocks+navigation"
]
=
self
.
blocks
elif
return_blocks
:
response
[
"blocks"
]
=
self
.
blocks
elif
return_nav
:
response
[
"navigation"
]
=
self
.
navigation
class
BlockInfo
(
object
):
"""
A class for encapsulating a block's information as needed during traversal of a block hierarchy.
"""
def
__init__
(
self
,
block
,
request_info
,
parent_block_info
=
None
):
# the block for which the recursion is being computed
self
.
block
=
block
# the type of the block
self
.
type
=
block
.
category
# the block's depth in the block hierarchy
self
.
depth
=
0
# the block's children
self
.
children
=
[]
# descendants_of_parent: the list of descendants for this block's parent
self
.
descendants_of_parent
=
[]
self
.
descendants_of_self
=
[]
# if a parent block was provided, update this block's data based on the parent's data
if
parent_block_info
:
# increment this block's depth value
self
.
depth
=
parent_block_info
.
depth
+
1
# set this blocks' descendants_of_parent
self
.
descendants_of_parent
=
parent_block_info
.
descendants_of_self
# add ourselves to the parent's children, if requested.
if
'children'
in
request_info
.
fields
:
parent_block_info
.
value
.
setdefault
(
"children"
,
[])
.
append
(
unicode
(
block
.
location
))
# the block's data to include in the response
self
.
value
=
{
"id"
:
unicode
(
block
.
location
),
"type"
:
self
.
type
,
"display_name"
:
block
.
display_name
,
"web_url"
:
reverse
(
"jump_to"
,
kwargs
=
{
"course_id"
:
unicode
(
request_info
.
course
.
id
),
"location"
:
unicode
(
block
.
location
)},
request
=
request_info
.
request
,
),
"block_url"
:
reverse
(
"courseware.views.render_xblock"
,
kwargs
=
{
"usage_key_string"
:
unicode
(
block
.
location
)},
request
=
request_info
.
request
,
),
}
@view_course_access
(
depth
=
None
)
def
list
(
self
,
request
,
course
,
return_blocks
=
True
,
return_nav
=
True
,
*
args
,
**
kwargs
):
"""
REST API endpoint for listing all the blocks and/or navigation information in the course,
while regarding user access and roles.
Arguments:
request - Django request object
course - course module object
return_blocks - If true, returns the blocks information for the course.
return_nav - If true, returns the navigation information for the course.
"""
# set starting point
start_block
=
course
# initialize request and result objects
request_info
=
self
.
RequestInfo
(
request
,
course
)
result_data
=
self
.
ResultData
(
return_blocks
,
return_nav
)
# create and populate a field data cache by pre-fetching for the course (with depth=None)
request_info
.
field_data_cache
=
FieldDataCache
.
cache_for_descriptor_descendents
(
course
.
id
,
request
.
user
,
course
,
depth
=
None
,
)
# start the recursion with the start_block
self
.
recurse_blocks_nav
(
request_info
,
result_data
,
self
.
BlockInfo
(
start_block
,
request_info
))
# return response
response
=
{
"root"
:
unicode
(
start_block
.
location
)}
result_data
.
update_response
(
response
,
return_blocks
,
return_nav
)
return
Response
(
response
)
def
recurse_blocks_nav
(
self
,
request_info
,
result_data
,
block_info
):
"""
A depth-first recursive function that supports calculation of both the list of blocks in the course
and the navigation information up to the requested navigation_depth of the course.
Arguments:
request_info - Object encapsulating the request information.
result_data - Running result data that is updated during the recursion.
block_info - Information about the current block in the recursion.
"""
# bind user data to the block
block_info
.
block
=
get_module_for_descriptor
(
request_info
.
request
.
user
,
request_info
.
request
,
block_info
.
block
,
request_info
.
field_data_cache
,
request_info
.
course
.
id
,
course
=
request_info
.
course
)
# verify the user has access to this block
if
(
block_info
.
block
is
None
or
not
has_access
(
request_info
.
request
.
user
,
'load'
,
block_info
.
block
,
course_key
=
request_info
.
course
.
id
)):
return
# add the block's value to the result
result_data
.
blocks
[
unicode
(
block_info
.
block
.
location
)]
=
block_info
.
value
# descendants
self
.
update_descendants
(
request_info
,
result_data
,
block_info
)
# children: recursively call the function for each of the children, while supporting dynamic children.
if
block_info
.
block
.
has_children
:
block_info
.
children
=
get_dynamic_descriptor_children
(
block_info
.
block
,
request_info
.
request
.
user
.
id
)
for
child
in
block_info
.
children
:
self
.
recurse_blocks_nav
(
request_info
,
result_data
,
self
.
BlockInfo
(
child
,
request_info
,
parent_block_info
=
block_info
)
)
# block count
self
.
update_block_count
(
request_info
,
result_data
,
block_info
)
# block JSON data
self
.
add_block_json
(
request_info
,
block_info
)
# multi-device support
if
'multi_device'
in
request_info
.
fields
:
block_info
.
value
[
'multi_device'
]
=
block_info
.
block
.
has_support
(
getattr
(
block_info
.
block
,
'student_view'
,
None
),
'multi_device'
)
# additional fields
self
.
add_additional_fields
(
request_info
,
block_info
)
def
update_descendants
(
self
,
request_info
,
result_data
,
block_info
):
"""
Updates the descendants data for the current block.
The current block is added to its parent's descendants if it is visible in the navigation
(i.e., the 'hide_from_toc' setting is False).
Additionally, the block's depth is compared with the navigation_depth parameter to determine whether the
descendants of the block should be added to its own descendants (if block.depth <= navigation_depth)
or to the descendants of the block's parents (if block.depth > navigation_depth).
block_info.descendants_of_self is the list of descendants that is passed to this block's children.
It should be either:
descendants_of_parent - if this block's depth is greater than the requested navigation_depth.
a dangling [] - if this block's hide_from_toc is True.
a referenced [] in navigation[block.location]["descendants"] - if this block's depth is within
the requested navigation depth.
"""
# Blocks with the 'hide_from_toc' setting are accessible, just not navigatable from the table-of-contents.
# If the 'hide_from_toc' setting is set on the block, do not add this block to the parent's descendants
# list and let the block's descendants add themselves to a dangling (unreferenced) descendants list.
if
not
block_info
.
block
.
hide_from_toc
:
# add this block to the parent's descendants
block_info
.
descendants_of_parent
.
append
(
unicode
(
block_info
.
block
.
location
))
# if this block's depth in the hierarchy is greater than the requested navigation depth,
# have the block's descendants add themselves to the parent's descendants.
if
block_info
.
depth
>
request_info
.
navigation_depth
:
block_info
.
descendants_of_self
=
block_info
.
descendants_of_parent
# otherwise, have the block's descendants add themselves to this block's descendants by
# referencing/attaching descendants_of_self from this block's navigation value.
else
:
result_data
.
navigation
.
setdefault
(
unicode
(
block_info
.
block
.
location
),
{}
)[
"descendants"
]
=
block_info
.
descendants_of_self
def
update_block_count
(
self
,
request_info
,
result_data
,
block_info
):
"""
For all the block types that are requested to be counted, include the count of that block type as
aggregated from the block's descendants.
Arguments:
request_info - Object encapsulating the request information.
result_data - Running result data that is updated during the recursion.
block_info - Information about the current block in the recursion.
"""
for
b_type
in
request_info
.
block_count
:
block_info
.
value
.
setdefault
(
"block_count"
,
{})[
b_type
]
=
(
sum
(
result_data
.
blocks
.
get
(
unicode
(
child
.
location
),
{})
.
get
(
"block_count"
,
{})
.
get
(
b_type
,
0
)
for
child
in
block_info
.
children
)
+
(
1
if
b_type
==
block_info
.
type
else
0
)
)
def
add_block_json
(
self
,
request_info
,
block_info
):
"""
If the JSON data for this block's type is requested, and the block supports the 'student_view_json'
method, add the response from the 'student_view_json" method as the data for the block.
"""
if
block_info
.
type
in
request_info
.
block_json
:
if
getattr
(
block_info
.
block
,
'student_view_data'
,
None
):
block_info
.
value
[
"block_json"
]
=
block_info
.
block
.
student_view_data
(
context
=
request_info
.
block_json
[
block_info
.
type
]
)
# A mapping of API-exposed field names to xBlock field names and API field defaults.
BlockApiField
=
namedtuple
(
'BlockApiField'
,
'block_field_name api_field_default'
)
FIELD_MAP
=
{
'graded'
:
BlockApiField
(
block_field_name
=
'graded'
,
api_field_default
=
False
),
'format'
:
BlockApiField
(
block_field_name
=
'format'
,
api_field_default
=
None
),
}
def
add_additional_fields
(
self
,
request_info
,
block_info
):
"""
Add additional field names and values of the block as requested in the request_info.
"""
for
field_name
in
request_info
.
fields
:
if
field_name
in
self
.
FIELD_MAP
:
block_info
.
value
[
field_name
]
=
getattr
(
block_info
.
block
,
self
.
FIELD_MAP
[
field_name
]
.
block_field_name
,
self
.
FIELD_MAP
[
field_name
]
.
api_field_default
,
)
def
perform_authentication
(
self
,
request
):
"""
Ensures that the user is authenticated (e.g. not an AnonymousUser)
"""
super
(
CourseBlocksAndNavigation
,
self
)
.
perform_authentication
(
request
)
if
request
.
user
.
is_anonymous
():
raise
AuthenticationFailed
lms/djangoapps/mobile_api/social_facebook/__init__.py
deleted
100644 → 0
View file @
8f7c158f
"""
Social Facebook API
"""
# TODO
# There are still some performance and scalability issues that should be
# addressed for the various endpoints in this social_facebook djangoapp.
#
# For the Courses and Friends API:
# For both endpoints, we are retrieving the same data from the Facebook server.
# We are then simply organizing and filtering that data differently for each endpoint.
#
# Here are 3 ideas that can be explored further:
#
# Option 1. The app can just call one endpoint that provides a mapping between CourseIDs and Friends,
# and then cache that data once. The reverse map from Friends to CourseIDs can then be created on the app side.
#
# Option 2. The app once again calls just one endpoint (since the same data is computed for both),
# and caches the data once. The difference from #1 is that the server does the computation of the reverse-map and
# sends both maps down to the client. It's a tradeoff between bandwidth and client-side computation. So the payload
# could be something like:
#
# {
# courses: [
# {course_id: "c/ourse/1", friend_indices: [1, 2, 3]},
# {course_id: "c/ourse/2", friend_indices: [3, 4, 5]},
# ..
# ],
# friends: [
# {username: "friend1", facebook_id: "xxx", course_indices: [2, 7, 9]},
# {username: "friend2", facebook_id: "yyy", course_indices: [1, 4, 3]},
# ...
# ]
# }
#
# Option 3. Alternatively, continue to have separate endpoints, but have both endpoints call the same underlying method
# with a built-in cache.
#
# All 3 options can make use of a common cache of results from FB.
#
# At a minimum, some performance/load testing would need to be done
# so we have an idea of these endpoints' limitations and thresholds.
lms/djangoapps/mobile_api/social_facebook/courses/__init__.py
deleted
100644 → 0
View file @
8f7c158f
"""
Courses API
"""
lms/djangoapps/mobile_api/social_facebook/courses/models.py
deleted
100644 → 0
View file @
8f7c158f
"""
A models.py is required to make this an app (until we move to Django 1.7)
"""
lms/djangoapps/mobile_api/social_facebook/courses/serializers.py
deleted
100644 → 0
View file @
8f7c158f
"""
Serializer for courses API
"""
from
rest_framework
import
serializers
class
CoursesWithFriendsSerializer
(
serializers
.
Serializer
):
"""
Serializes oauth token for facebook groups request
"""
oauth_token
=
serializers
.
CharField
(
required
=
True
)
lms/djangoapps/mobile_api/social_facebook/courses/tests.py
deleted
100644 → 0
View file @
8f7c158f
# pylint: disable=E1101, W0201
"""
Tests for Courses
"""
import
httpretty
import
json
from
django.core.urlresolvers
import
reverse
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
opaque_keys.edx.keys
import
CourseKey
from
..test_utils
import
SocialFacebookTestCase
class
TestCourses
(
SocialFacebookTestCase
):
"""
Tests for /api/mobile/v0.5/courses/...
"""
def
setUp
(
self
):
super
(
TestCourses
,
self
)
.
setUp
()
self
.
course
=
CourseFactory
.
create
(
mobile_available
=
True
)
@httpretty.activate
def
test_one_course_with_friends
(
self
):
self
.
user_create_and_signin
(
1
)
self
.
link_edx_account_to_social
(
self
.
users
[
1
],
self
.
BACKEND
,
self
.
USERS
[
1
][
'FB_ID'
])
self
.
set_sharing_preferences
(
self
.
users
[
1
],
True
)
self
.
set_facebook_interceptor_for_friends
(
{
'data'
:
[{
'name'
:
self
.
USERS
[
1
][
'USERNAME'
],
'id'
:
self
.
USERS
[
1
][
'FB_ID'
]}]}
)
self
.
enroll_in_course
(
self
.
users
[
1
],
self
.
course
)
url
=
reverse
(
'courses-with-friends'
)
response
=
self
.
client
.
get
(
url
,
{
'oauth_token'
:
self
.
_FB_USER_ACCESS_TOKEN
})
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
self
.
course
.
id
,
CourseKey
.
from_string
(
response
.
data
[
0
][
'course'
][
'id'
]))
# pylint: disable=E1101
@httpretty.activate
def
test_two_courses_with_friends
(
self
):
self
.
user_create_and_signin
(
1
)
self
.
link_edx_account_to_social
(
self
.
users
[
1
],
self
.
BACKEND
,
self
.
USERS
[
1
][
'FB_ID'
])
self
.
set_sharing_preferences
(
self
.
users
[
1
],
True
)
self
.
enroll_in_course
(
self
.
users
[
1
],
self
.
course
)
self
.
course_2
=
CourseFactory
.
create
(
mobile_available
=
True
)
self
.
enroll_in_course
(
self
.
users
[
1
],
self
.
course_2
)
self
.
set_facebook_interceptor_for_friends
(
{
'data'
:
[{
'name'
:
self
.
USERS
[
2
][
'USERNAME'
],
'id'
:
self
.
USERS
[
1
][
'FB_ID'
]}]}
)
url
=
reverse
(
'courses-with-friends'
)
response
=
self
.
client
.
get
(
url
,
{
'oauth_token'
:
self
.
_FB_USER_ACCESS_TOKEN
})
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
self
.
course
.
id
,
CourseKey
.
from_string
(
response
.
data
[
0
][
'course'
][
'id'
]))
# pylint: disable=E1101
self
.
assertEqual
(
self
.
course_2
.
id
,
CourseKey
.
from_string
(
response
.
data
[
1
][
'course'
][
'id'
]))
# pylint: disable=E1101
@httpretty.activate
def
test_three_courses_but_only_two_unique
(
self
):
self
.
user_create_and_signin
(
1
)
self
.
link_edx_account_to_social
(
self
.
users
[
1
],
self
.
BACKEND
,
self
.
USERS
[
1
][
'FB_ID'
])
self
.
set_sharing_preferences
(
self
.
users
[
1
],
True
)
self
.
course_2
=
CourseFactory
.
create
(
mobile_available
=
True
)
self
.
enroll_in_course
(
self
.
users
[
1
],
self
.
course_2
)
self
.
enroll_in_course
(
self
.
users
[
1
],
self
.
course
)
self
.
user_create_and_signin
(
2
)
self
.
link_edx_account_to_social
(
self
.
users
[
2
],
self
.
BACKEND
,
self
.
USERS
[
2
][
'FB_ID'
])
self
.
set_sharing_preferences
(
self
.
users
[
2
],
True
)
# Enroll another user in course_2
self
.
enroll_in_course
(
self
.
users
[
2
],
self
.
course_2
)
self
.
set_facebook_interceptor_for_friends
(
{
'data'
:
[
{
'name'
:
self
.
USERS
[
1
][
'USERNAME'
],
'id'
:
self
.
USERS
[
1
][
'FB_ID'
]},
{
'name'
:
self
.
USERS
[
2
][
'USERNAME'
],
'id'
:
self
.
USERS
[
2
][
'FB_ID'
]},
]}
)
url
=
reverse
(
'courses-with-friends'
)
response
=
self
.
client
.
get
(
url
,
{
'oauth_token'
:
self
.
_FB_USER_ACCESS_TOKEN
})
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
self
.
course
.
id
,
CourseKey
.
from_string
(
response
.
data
[
0
][
'course'
][
'id'
]))
# pylint: disable=E1101
self
.
assertEqual
(
self
.
course_2
.
id
,
CourseKey
.
from_string
(
response
.
data
[
1
][
'course'
][
'id'
]))
# pylint: disable=E1101
# Assert that only two courses are returned
self
.
assertEqual
(
len
(
response
.
data
),
2
)
# pylint: disable=E1101
@httpretty.activate
def
test_two_courses_with_two_friends_on_different_paged_results
(
self
):
self
.
user_create_and_signin
(
1
)
self
.
link_edx_account_to_social
(
self
.
users
[
1
],
self
.
BACKEND
,
self
.
USERS
[
1
][
'FB_ID'
])
self
.
set_sharing_preferences
(
self
.
users
[
1
],
True
)
self
.
enroll_in_course
(
self
.
users
[
1
],
self
.
course
)
self
.
user_create_and_signin
(
2
)
self
.
link_edx_account_to_social
(
self
.
users
[
2
],
self
.
BACKEND
,
self
.
USERS
[
2
][
'FB_ID'
])
self
.
set_sharing_preferences
(
self
.
users
[
2
],
True
)
self
.
course_2
=
CourseFactory
.
create
(
mobile_available
=
True
)
self
.
enroll_in_course
(
self
.
users
[
2
],
self
.
course_2
)
self
.
set_facebook_interceptor_for_friends
(
{
'data'
:
[{
'name'
:
self
.
USERS
[
1
][
'USERNAME'
],
'id'
:
self
.
USERS
[
1
][
'FB_ID'
]}],
"paging"
:
{
"next"
:
"https://graph.facebook.com/v2.2/me/friends/next"
},
"summary"
:
{
"total_count"
:
652
}
}
)
# Set the interceptor for the paged
httpretty
.
register_uri
(
httpretty
.
GET
,
"https://graph.facebook.com/v2.2/me/friends/next"
,
body
=
json
.
dumps
(
{
"data"
:
[{
'name'
:
self
.
USERS
[
2
][
'USERNAME'
],
'id'
:
self
.
USERS
[
2
][
'FB_ID'
]}],
"paging"
:
{
"previous"
:
"https://graph.facebook.com/v2.2/10154805434030300/friends?limit=25&offset=25"
},
"summary"
:
{
"total_count"
:
652
}
}
),
status
=
201
)
url
=
reverse
(
'courses-with-friends'
)
response
=
self
.
client
.
get
(
url
,
{
'oauth_token'
:
self
.
_FB_USER_ACCESS_TOKEN
})
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
self
.
course
.
id
,
CourseKey
.
from_string
(
response
.
data
[
0
][
'course'
][
'id'
]))
# pylint: disable=E1101
self
.
assertEqual
(
self
.
course_2
.
id
,
CourseKey
.
from_string
(
response
.
data
[
1
][
'course'
][
'id'
]))
# pylint: disable=E1101
@httpretty.activate
def
test_no_courses_with_friends_because_sharing_pref_off
(
self
):
self
.
user_create_and_signin
(
1
)
self
.
link_edx_account_to_social
(
self
.
users
[
1
],
self
.
BACKEND
,
self
.
USERS
[
1
][
'FB_ID'
])
self
.
set_sharing_preferences
(
self
.
users
[
1
],
False
)
self
.
set_facebook_interceptor_for_friends
(
{
'data'
:
[{
'name'
:
self
.
USERS
[
1
][
'USERNAME'
],
'id'
:
self
.
USERS
[
1
][
'FB_ID'
]}]}
)
self
.
enroll_in_course
(
self
.
users
[
1
],
self
.
course
)
url
=
reverse
(
'courses-with-friends'
)
response
=
self
.
client
.
get
(
url
,
{
'oauth_token'
:
self
.
_FB_USER_ACCESS_TOKEN
})
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
len
(
response
.
data
),
0
)
@httpretty.activate
def
test_no_courses_with_friends_because_no_auth_token
(
self
):
self
.
user_create_and_signin
(
1
)
self
.
link_edx_account_to_social
(
self
.
users
[
1
],
self
.
BACKEND
,
self
.
USERS
[
1
][
'FB_ID'
])
self
.
set_sharing_preferences
(
self
.
users
[
1
],
False
)
self
.
set_facebook_interceptor_for_friends
(
{
'data'
:
[{
'name'
:
self
.
USERS
[
1
][
'USERNAME'
],
'id'
:
self
.
USERS
[
1
][
'FB_ID'
]}]}
)
self
.
enroll_in_course
(
self
.
users
[
1
],
self
.
course
)
url
=
reverse
(
'courses-with-friends'
)
response
=
self
.
client
.
get
(
url
)
self
.
assertEqual
(
response
.
status_code
,
400
)
lms/djangoapps/mobile_api/social_facebook/courses/urls.py
deleted
100644 → 0
View file @
8f7c158f
"""
URLs for courses API
"""
from
django.conf.urls
import
patterns
,
url
from
.views
import
CoursesWithFriends
urlpatterns
=
patterns
(
'mobile_api.social_facebook.courses.views'
,
url
(
r'^friends$'
,
CoursesWithFriends
.
as_view
(),
name
=
'courses-with-friends'
),
)
lms/djangoapps/mobile_api/social_facebook/courses/views.py
deleted
100644 → 0
View file @
8f7c158f
"""
Views for courses info API
"""
from
rest_framework
import
generics
,
status
from
rest_framework.response
import
Response
from
courseware.access
import
is_mobile_available_for_user
from
student.models
import
CourseEnrollment
from
lms.djangoapps.mobile_api.social_facebook.courses
import
serializers
from
...users.serializers
import
CourseEnrollmentSerializer
from
...utils
import
mobile_view
from
..utils
import
get_friends_from_facebook
,
get_linked_edx_accounts
,
share_with_facebook_friends
@mobile_view
()
class
CoursesWithFriends
(
generics
.
ListAPIView
):
"""
**Use Case**
API endpoint for retrieving all the courses that a user's friends are in.
Note that only friends that allow their courses to be shared will be included.
**Example request**
GET /api/mobile/v0.5/social/facebook/courses/friends
**Response Values**
See UserCourseEnrollmentsList in lms/djangoapps/mobile_api/users for the structure of the response values.
"""
serializer_class
=
serializers
.
CoursesWithFriendsSerializer
def
list
(
self
,
request
,
*
args
,
**
kwargs
):
serializer
=
self
.
get_serializer
(
data
=
request
.
GET
)
if
not
serializer
.
is_valid
():
return
Response
(
serializer
.
errors
,
status
=
status
.
HTTP_400_BAD_REQUEST
)
# Get friends from Facebook
result
=
get_friends_from_facebook
(
serializer
)
if
not
isinstance
(
result
,
list
):
return
result
friends_that_are_edx_users
=
get_linked_edx_accounts
(
result
)
# Filter by sharing preferences
users_with_sharing
=
[
friend
for
friend
in
friends_that_are_edx_users
if
share_with_facebook_friends
(
friend
)
]
# Get unique enrollments
enrollments
=
[]
for
friend
in
users_with_sharing
:
query_set
=
CourseEnrollment
.
objects
.
filter
(
user_id
=
friend
[
'edX_id'
]
)
.
exclude
(
course_id__in
=
[
enrollment
.
course_id
for
enrollment
in
enrollments
])
enrollments
.
extend
(
query_set
)
# Get course objects
courses
=
[
enrollment
for
enrollment
in
enrollments
if
enrollment
.
course
and
is_mobile_available_for_user
(
self
.
request
.
user
,
enrollment
.
course
)
]
serializer
=
CourseEnrollmentSerializer
(
courses
,
context
=
{
'request'
:
request
},
many
=
True
)
return
Response
(
serializer
.
data
)
lms/djangoapps/mobile_api/social_facebook/friends/__init__.py
deleted
100644 → 0
View file @
8f7c158f
"""
Friends API
"""
lms/djangoapps/mobile_api/social_facebook/friends/models.py
deleted
100644 → 0
View file @
8f7c158f
"""
A models.py is required to make this an app (until we move to Django 1.7)
"""
lms/djangoapps/mobile_api/social_facebook/friends/serializers.py
deleted
100644 → 0
View file @
8f7c158f
"""
Serializer for Friends API
"""
from
rest_framework
import
serializers
class
FriendsInCourseSerializer
(
serializers
.
Serializer
):
"""
Serializes oauth token for facebook groups request
"""
oauth_token
=
serializers
.
CharField
(
required
=
True
)
lms/djangoapps/mobile_api/social_facebook/friends/tests.py
deleted
100644 → 0
View file @
8f7c158f
# pylint: disable=E1101
"""
Tests for friends
"""
import
json
import
httpretty
from
django.core.urlresolvers
import
reverse
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
..test_utils
import
SocialFacebookTestCase
class
TestFriends
(
SocialFacebookTestCase
):
"""
Tests for /api/mobile/v0.5/friends/...
"""
def
setUp
(
self
):
super
(
TestFriends
,
self
)
.
setUp
()
self
.
course
=
CourseFactory
.
create
()
@httpretty.activate
def
test_no_friends_enrolled
(
self
):
# User 1 set up
self
.
user_create_and_signin
(
1
)
# Link user_1's edX account to FB
self
.
link_edx_account_to_social
(
self
.
users
[
1
],
self
.
BACKEND
,
self
.
USERS
[
1
][
'FB_ID'
])
self
.
set_sharing_preferences
(
self
.
users
[
1
],
True
)
# Set the interceptor
self
.
set_facebook_interceptor_for_friends
(
{
'data'
:
[
{
'name'
:
self
.
USERS
[
1
][
'USERNAME'
],
'id'
:
self
.
USERS
[
1
][
'FB_ID'
]},
{
'name'
:
self
.
USERS
[
2
][
'USERNAME'
],
'id'
:
self
.
USERS
[
2
][
'FB_ID'
]},
{
'name'
:
self
.
USERS
[
3
][
'USERNAME'
],
'id'
:
self
.
USERS
[
3
][
'FB_ID'
]},
]
}
)
course_id
=
unicode
(
self
.
course
.
id
)
url
=
reverse
(
'friends-in-course'
,
kwargs
=
{
"course_id"
:
course_id
})
response
=
self
.
client
.
get
(
url
,
{
'format'
:
'json'
,
'oauth_token'
:
self
.
_FB_USER_ACCESS_TOKEN
})
# Assert that no friends are returned
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertTrue
(
'friends'
in
response
.
data
and
len
(
response
.
data
[
'friends'
])
==
0
)
@httpretty.activate
def
test_no_friends_on_facebook
(
self
):
# User 1 set up
self
.
user_create_and_signin
(
1
)
# Enroll user_1 in the course
self
.
enroll_in_course
(
self
.
users
[
1
],
self
.
course
)
self
.
set_sharing_preferences
(
self
.
users
[
1
],
True
)
# Link user_1's edX account to FB
self
.
link_edx_account_to_social
(
self
.
users
[
1
],
self
.
BACKEND
,
self
.
USERS
[
1
][
'FB_ID'
])
# Set the interceptor
self
.
set_facebook_interceptor_for_friends
({
'data'
:
[]})
course_id
=
unicode
(
self
.
course
.
id
)
url
=
reverse
(
'friends-in-course'
,
kwargs
=
{
"course_id"
:
course_id
})
response
=
self
.
client
.
get
(
url
,
{
'format'
:
'json'
,
'oauth_token'
:
self
.
_FB_USER_ACCESS_TOKEN
}
)
# Assert that no friends are returned
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertTrue
(
'friends'
in
response
.
data
and
len
(
response
.
data
[
'friends'
])
==
0
)
@httpretty.activate
def
test_no_friends_linked_to_edx
(
self
):
# User 1 set up
self
.
user_create_and_signin
(
1
)
# Enroll user_1 in the course
self
.
enroll_in_course
(
self
.
users
[
1
],
self
.
course
)
self
.
set_sharing_preferences
(
self
.
users
[
1
],
True
)
# User 2 set up
self
.
user_create_and_signin
(
2
)
# Enroll user_2 in the course
self
.
enroll_in_course
(
self
.
users
[
2
],
self
.
course
)
self
.
set_sharing_preferences
(
self
.
users
[
2
],
True
)
# User 3 set up
self
.
user_create_and_signin
(
3
)
# Enroll user_3 in the course
self
.
enroll_in_course
(
self
.
users
[
3
],
self
.
course
)
self
.
set_sharing_preferences
(
self
.
users
[
3
],
True
)
# Set the interceptor
self
.
set_facebook_interceptor_for_friends
(
{
'data'
:
[
{
'name'
:
self
.
USERS
[
1
][
'USERNAME'
],
'id'
:
self
.
USERS
[
1
][
'FB_ID'
]},
{
'name'
:
self
.
USERS
[
2
][
'USERNAME'
],
'id'
:
self
.
USERS
[
2
][
'FB_ID'
]},
{
'name'
:
self
.
USERS
[
3
][
'USERNAME'
],
'id'
:
self
.
USERS
[
3
][
'FB_ID'
]},
]
}
)
course_id
=
unicode
(
self
.
course
.
id
)
url
=
reverse
(
'friends-in-course'
,
kwargs
=
{
"course_id"
:
course_id
})
response
=
self
.
client
.
get
(
url
,
{
'format'
:
'json'
,
'oauth_token'
:
self
.
_FB_USER_ACCESS_TOKEN
}
)
# Assert that no friends are returned
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertTrue
(
'friends'
in
response
.
data
and
len
(
response
.
data
[
'friends'
])
==
0
)
@httpretty.activate
def
test_no_friends_share_settings_false
(
self
):
# User 1 set up
self
.
user_create_and_signin
(
1
)
self
.
enroll_in_course
(
self
.
users
[
1
],
self
.
course
)
self
.
link_edx_account_to_social
(
self
.
users
[
1
],
self
.
BACKEND
,
self
.
USERS
[
1
][
'FB_ID'
])
self
.
set_sharing_preferences
(
self
.
users
[
1
],
False
)
self
.
set_facebook_interceptor_for_friends
(
{
'data'
:
[
{
'name'
:
self
.
USERS
[
1
][
'USERNAME'
],
'id'
:
self
.
USERS
[
1
][
'FB_ID'
]},
{
'name'
:
self
.
USERS
[
2
][
'USERNAME'
],
'id'
:
self
.
USERS
[
2
][
'FB_ID'
]},
{
'name'
:
self
.
USERS
[
3
][
'USERNAME'
],
'id'
:
self
.
USERS
[
3
][
'FB_ID'
]},
]
}
)
url
=
reverse
(
'friends-in-course'
,
kwargs
=
{
"course_id"
:
unicode
(
self
.
course
.
id
)})
response
=
self
.
client
.
get
(
url
,
{
'format'
:
'json'
,
'oauth_token'
:
self
.
_FB_USER_ACCESS_TOKEN
})
# Assert that USERNAME_1 is returned
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertTrue
(
'friends'
in
response
.
data
)
self
.
assertTrue
(
'friends'
in
response
.
data
and
len
(
response
.
data
[
'friends'
])
==
0
)
@httpretty.activate
def
test_no_friends_no_oauth_token
(
self
):
# User 1 set up
self
.
user_create_and_signin
(
1
)
self
.
enroll_in_course
(
self
.
users
[
1
],
self
.
course
)
self
.
link_edx_account_to_social
(
self
.
users
[
1
],
self
.
BACKEND
,
self
.
USERS
[
1
][
'FB_ID'
])
self
.
set_sharing_preferences
(
self
.
users
[
1
],
False
)
self
.
set_facebook_interceptor_for_friends
(
{
'data'
:
[
{
'name'
:
self
.
USERS
[
1
][
'USERNAME'
],
'id'
:
self
.
USERS
[
1
][
'FB_ID'
]},
{
'name'
:
self
.
USERS
[
2
][
'USERNAME'
],
'id'
:
self
.
USERS
[
2
][
'FB_ID'
]},
{
'name'
:
self
.
USERS
[
3
][
'USERNAME'
],
'id'
:
self
.
USERS
[
3
][
'FB_ID'
]},
]
}
)
url
=
reverse
(
'friends-in-course'
,
kwargs
=
{
"course_id"
:
unicode
(
self
.
course
.
id
)})
response
=
self
.
client
.
get
(
url
,
{
'format'
:
'json'
})
# Assert that USERNAME_1 is returned
self
.
assertEqual
(
response
.
status_code
,
400
)
@httpretty.activate
def
test_one_friend_in_course
(
self
):
# User 1 set up
self
.
user_create_and_signin
(
1
)
self
.
enroll_in_course
(
self
.
users
[
1
],
self
.
course
)
self
.
link_edx_account_to_social
(
self
.
users
[
1
],
self
.
BACKEND
,
self
.
USERS
[
1
][
'FB_ID'
])
self
.
set_sharing_preferences
(
self
.
users
[
1
],
True
)
self
.
set_facebook_interceptor_for_friends
(
{
'data'
:
[
{
'name'
:
self
.
USERS
[
1
][
'USERNAME'
],
'id'
:
self
.
USERS
[
1
][
'FB_ID'
]},
{
'name'
:
self
.
USERS
[
2
][
'USERNAME'
],
'id'
:
self
.
USERS
[
2
][
'FB_ID'
]},
{
'name'
:
self
.
USERS
[
3
][
'USERNAME'
],
'id'
:
self
.
USERS
[
3
][
'FB_ID'
]},
]
}
)
url
=
reverse
(
'friends-in-course'
,
kwargs
=
{
"course_id"
:
unicode
(
self
.
course
.
id
)})
response
=
self
.
client
.
get
(
url
,
{
'format'
:
'json'
,
'oauth_token'
:
self
.
_FB_USER_ACCESS_TOKEN
})
# Assert that USERNAME_1 is returned
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertTrue
(
'friends'
in
response
.
data
)
self
.
assertTrue
(
'id'
in
response
.
data
[
'friends'
][
0
])
self
.
assertTrue
(
response
.
data
[
'friends'
][
0
][
'id'
]
==
self
.
USERS
[
1
][
'FB_ID'
])
self
.
assertTrue
(
'name'
in
response
.
data
[
'friends'
][
0
])
self
.
assertTrue
(
response
.
data
[
'friends'
][
0
][
'name'
]
==
self
.
USERS
[
1
][
'USERNAME'
])
@httpretty.activate
def
test_three_friends_in_course
(
self
):
# User 1 set up
self
.
user_create_and_signin
(
1
)
self
.
enroll_in_course
(
self
.
users
[
1
],
self
.
course
)
self
.
link_edx_account_to_social
(
self
.
users
[
1
],
self
.
BACKEND
,
self
.
USERS
[
1
][
'FB_ID'
])
self
.
set_sharing_preferences
(
self
.
users
[
1
],
True
)
# User 2 set up
self
.
user_create_and_signin
(
2
)
self
.
enroll_in_course
(
self
.
users
[
2
],
self
.
course
)
self
.
link_edx_account_to_social
(
self
.
users
[
2
],
self
.
BACKEND
,
self
.
USERS
[
2
][
'FB_ID'
])
self
.
set_sharing_preferences
(
self
.
users
[
2
],
True
)
# User 3 set up
self
.
user_create_and_signin
(
3
)
self
.
enroll_in_course
(
self
.
users
[
3
],
self
.
course
)
self
.
link_edx_account_to_social
(
self
.
users
[
3
],
self
.
BACKEND
,
self
.
USERS
[
3
][
'FB_ID'
])
self
.
set_sharing_preferences
(
self
.
users
[
3
],
True
)
self
.
set_facebook_interceptor_for_friends
(
{
'data'
:
[
{
'name'
:
self
.
USERS
[
1
][
'USERNAME'
],
'id'
:
self
.
USERS
[
1
][
'FB_ID'
]},
{
'name'
:
self
.
USERS
[
2
][
'USERNAME'
],
'id'
:
self
.
USERS
[
2
][
'FB_ID'
]},
{
'name'
:
self
.
USERS
[
3
][
'USERNAME'
],
'id'
:
self
.
USERS
[
3
][
'FB_ID'
]},
]
}
)
url
=
reverse
(
'friends-in-course'
,
kwargs
=
{
"course_id"
:
unicode
(
self
.
course
.
id
)})
response
=
self
.
client
.
get
(
url
,
{
'format'
:
'json'
,
'oauth_token'
:
self
.
_FB_USER_ACCESS_TOKEN
}
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertTrue
(
'friends'
in
response
.
data
)
# Assert that USERNAME_1 is returned
self
.
assertTrue
(
'id'
in
response
.
data
[
'friends'
][
0
]
and
response
.
data
[
'friends'
][
0
][
'id'
]
==
self
.
USERS
[
1
][
'FB_ID'
]
)
self
.
assertTrue
(
'name'
in
response
.
data
[
'friends'
][
0
]
and
response
.
data
[
'friends'
][
0
][
'name'
]
==
self
.
USERS
[
1
][
'USERNAME'
]
)
# Assert that USERNAME_2 is returned
self
.
assertTrue
(
'id'
in
response
.
data
[
'friends'
][
1
]
and
response
.
data
[
'friends'
][
1
][
'id'
]
==
self
.
USERS
[
2
][
'FB_ID'
]
)
self
.
assertTrue
(
'name'
in
response
.
data
[
'friends'
][
1
]
and
response
.
data
[
'friends'
][
1
][
'name'
]
==
self
.
USERS
[
2
][
'USERNAME'
]
)
# Assert that USERNAME_3 is returned
self
.
assertTrue
(
'id'
in
response
.
data
[
'friends'
][
2
]
and
response
.
data
[
'friends'
][
2
][
'id'
]
==
self
.
USERS
[
3
][
'FB_ID'
]
)
self
.
assertTrue
(
'name'
in
response
.
data
[
'friends'
][
2
]
and
response
.
data
[
'friends'
][
2
][
'name'
]
==
self
.
USERS
[
3
][
'USERNAME'
]
)
@httpretty.activate
def
test_three_friends_in_paged_response
(
self
):
# User 1 set up
self
.
user_create_and_signin
(
1
)
self
.
enroll_in_course
(
self
.
users
[
1
],
self
.
course
)
self
.
link_edx_account_to_social
(
self
.
users
[
1
],
self
.
BACKEND
,
self
.
USERS
[
1
][
'FB_ID'
])
self
.
set_sharing_preferences
(
self
.
users
[
1
],
True
)
# User 2 set up
self
.
user_create_and_signin
(
2
)
self
.
enroll_in_course
(
self
.
users
[
2
],
self
.
course
)
self
.
link_edx_account_to_social
(
self
.
users
[
2
],
self
.
BACKEND
,
self
.
USERS
[
2
][
'FB_ID'
])
self
.
set_sharing_preferences
(
self
.
users
[
2
],
True
)
# User 3 set up
self
.
user_create_and_signin
(
3
)
self
.
enroll_in_course
(
self
.
users
[
3
],
self
.
course
)
self
.
link_edx_account_to_social
(
self
.
users
[
3
],
self
.
BACKEND
,
self
.
USERS
[
3
][
'FB_ID'
])
self
.
set_sharing_preferences
(
self
.
users
[
3
],
True
)
self
.
set_facebook_interceptor_for_friends
(
{
'data'
:
[{
'name'
:
self
.
USERS
[
1
][
'USERNAME'
],
'id'
:
self
.
USERS
[
1
][
'FB_ID'
]}],
"paging"
:
{
"next"
:
"https://graph.facebook.com/v2.2/me/friends/next_1"
},
"summary"
:
{
"total_count"
:
652
}
}
)
# Set the interceptor for the first paged content
httpretty
.
register_uri
(
httpretty
.
GET
,
"https://graph.facebook.com/v2.2/me/friends/next_1"
,
body
=
json
.
dumps
(
{
"data"
:
[{
'name'
:
self
.
USERS
[
2
][
'USERNAME'
],
'id'
:
self
.
USERS
[
2
][
'FB_ID'
]}],
"paging"
:
{
"next"
:
"https://graph.facebook.com/v2.2/me/friends/next_2"
},
"summary"
:
{
"total_count"
:
652
}
}
),
status
=
201
)
# Set the interceptor for the last paged content
httpretty
.
register_uri
(
httpretty
.
GET
,
"https://graph.facebook.com/v2.2/me/friends/next_2"
,
body
=
json
.
dumps
(
{
"data"
:
[{
'name'
:
self
.
USERS
[
3
][
'USERNAME'
],
'id'
:
self
.
USERS
[
3
][
'FB_ID'
]}],
"paging"
:
{
"previous"
:
"https://graph.facebook.com/v2.2/10154805434030300/friends?limit=25&offset=25"
},
"summary"
:
{
"total_count"
:
652
}
}
),
status
=
201
)
url
=
reverse
(
'friends-in-course'
,
kwargs
=
{
"course_id"
:
unicode
(
self
.
course
.
id
)})
response
=
self
.
client
.
get
(
url
,
{
'format'
:
'json'
,
'oauth_token'
:
self
.
_FB_USER_ACCESS_TOKEN
})
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertTrue
(
'friends'
in
response
.
data
)
# Assert that USERNAME_1 is returned
self
.
assertTrue
(
'id'
in
response
.
data
[
'friends'
][
0
])
self
.
assertTrue
(
response
.
data
[
'friends'
][
0
][
'id'
]
==
self
.
USERS
[
1
][
'FB_ID'
])
self
.
assertTrue
(
'name'
in
response
.
data
[
'friends'
][
0
])
self
.
assertTrue
(
response
.
data
[
'friends'
][
0
][
'name'
]
==
self
.
USERS
[
1
][
'USERNAME'
])
# Assert that USERNAME_2 is returned
self
.
assertTrue
(
'id'
in
response
.
data
[
'friends'
][
1
])
self
.
assertTrue
(
response
.
data
[
'friends'
][
1
][
'id'
]
==
self
.
USERS
[
2
][
'FB_ID'
])
self
.
assertTrue
(
'name'
in
response
.
data
[
'friends'
][
1
])
self
.
assertTrue
(
response
.
data
[
'friends'
][
1
][
'name'
]
==
self
.
USERS
[
2
][
'USERNAME'
])
# Assert that USERNAME_3 is returned
self
.
assertTrue
(
'id'
in
response
.
data
[
'friends'
][
2
])
self
.
assertTrue
(
response
.
data
[
'friends'
][
2
][
'id'
]
==
self
.
USERS
[
3
][
'FB_ID'
])
self
.
assertTrue
(
'name'
in
response
.
data
[
'friends'
][
2
])
self
.
assertTrue
(
response
.
data
[
'friends'
][
2
][
'name'
]
==
self
.
USERS
[
3
][
'USERNAME'
])
lms/djangoapps/mobile_api/social_facebook/friends/urls.py
deleted
100644 → 0
View file @
8f7c158f
"""
URLs for friends API
"""
from
django.conf.urls
import
patterns
,
url
from
django.conf
import
settings
from
.views
import
FriendsInCourse
urlpatterns
=
patterns
(
'mobile_api.social_facebook.friends.views'
,
url
(
r'^course/{}$'
.
format
(
settings
.
COURSE_ID_PATTERN
),
FriendsInCourse
.
as_view
(),
name
=
'friends-in-course'
),
)
lms/djangoapps/mobile_api/social_facebook/friends/views.py
deleted
100644 → 0
View file @
8f7c158f
"""
Views for friends info API
"""
from
rest_framework
import
generics
,
status
from
rest_framework.response
import
Response
from
opaque_keys.edx.keys
import
CourseKey
from
student.models
import
CourseEnrollment
from
...utils
import
mobile_view
from
..utils
import
get_friends_from_facebook
,
get_linked_edx_accounts
,
share_with_facebook_friends
from
lms.djangoapps.mobile_api.social_facebook.friends
import
serializers
@mobile_view
()
class
FriendsInCourse
(
generics
.
ListAPIView
):
"""
**Use Case**
API endpoint that returns all the users friends that are in the course specified.
Note that only friends that allow their courses to be shared will be included.
**Example request**:
GET /api/mobile/v0.5/social/facebook/friends/course/<course_id>
where course_id is in the form of /edX/DemoX/Demo_Course
**Response Values**
{
"friends": [
{
"name": "test",
"id": "12345",
},
...
]
}
"""
serializer_class
=
serializers
.
FriendsInCourseSerializer
def
list
(
self
,
request
,
*
args
,
**
kwargs
):
serializer
=
self
.
get_serializer
(
data
=
request
.
GET
)
if
not
serializer
.
is_valid
():
return
Response
(
serializer
.
errors
,
status
=
status
.
HTTP_400_BAD_REQUEST
)
# Get all the user's FB friends
result
=
get_friends_from_facebook
(
serializer
)
if
not
isinstance
(
result
,
list
):
return
result
def
is_member
(
friend
,
course_key
):
"""
Return true if friend is a member of the course specified by the course_key
"""
return
CourseEnrollment
.
objects
.
filter
(
course_id
=
course_key
,
user_id
=
friend
[
'edX_id'
]
)
.
count
()
==
1
# For each friend check if they are a linked edX user
friends_with_edx_users
=
get_linked_edx_accounts
(
result
)
# Filter by sharing preferences and enrollment in course
course_key
=
CourseKey
.
from_string
(
kwargs
[
'course_id'
])
friends_with_sharing_in_course
=
[
{
'id'
:
friend
[
'id'
],
'name'
:
friend
[
'name'
]}
for
friend
in
friends_with_edx_users
if
share_with_facebook_friends
(
friend
)
and
is_member
(
friend
,
course_key
)
]
return
Response
({
'friends'
:
friends_with_sharing_in_course
})
lms/djangoapps/mobile_api/social_facebook/groups/__init__.py
deleted
100644 → 0
View file @
8f7c158f
"""
Groups API
"""
lms/djangoapps/mobile_api/social_facebook/groups/models.py
deleted
100644 → 0
View file @
8f7c158f
"""
A models.py is required to make this an app (until we move to Django 1.7)
"""
lms/djangoapps/mobile_api/social_facebook/groups/serializers.py
deleted
100644 → 0
View file @
8f7c158f
"""
Serializer for user API
"""
from
rest_framework
import
serializers
from
django.core.validators
import
RegexValidator
class
GroupSerializer
(
serializers
.
Serializer
):
"""
Serializes facebook groups request
"""
name
=
serializers
.
CharField
(
max_length
=
150
)
description
=
serializers
.
CharField
(
max_length
=
200
,
required
=
False
)
privacy
=
serializers
.
ChoiceField
(
choices
=
[(
"open"
,
"open"
),
(
"closed"
,
"closed"
)],
required
=
False
)
class
GroupsMembersSerializer
(
serializers
.
Serializer
):
"""
Serializes facebook invitations request
"""
member_ids
=
serializers
.
CharField
(
required
=
True
,
validators
=
[
RegexValidator
(
regex
=
r'^([\d]+,?)*$'
,
message
=
'A comma separated list of member ids must be provided'
,
code
=
'member_ids error'
),
]
)
lms/djangoapps/mobile_api/social_facebook/groups/tests.py
deleted
100644 → 0
View file @
8f7c158f
"""
Tests for groups
"""
import
httpretty
from
ddt
import
ddt
,
data
from
django.conf
import
settings
from
django.core.urlresolvers
import
reverse
from
courseware.tests.factories
import
UserFactory
from
..test_utils
import
SocialFacebookTestCase
@ddt
class
TestGroups
(
SocialFacebookTestCase
):
"""
Tests for /api/mobile/v0.5/social/facebook/groups/...
"""
def
setUp
(
self
):
super
(
TestGroups
,
self
)
.
setUp
()
self
.
user
=
UserFactory
.
create
()
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
'test'
)
# Group Creation and Deletion Tests
@httpretty.activate
def
test_create_new_open_group
(
self
):
group_id
=
'12345678'
status_code
=
200
self
.
set_facebook_interceptor_for_access_token
()
self
.
set_facebook_interceptor_for_groups
({
'id'
:
group_id
},
status_code
)
url
=
reverse
(
'create-delete-group'
,
kwargs
=
{
'group_id'
:
''
})
response
=
self
.
client
.
post
(
url
,
{
'name'
:
'TheBestGroup'
,
'description'
:
'The group for the best people'
,
'privacy'
:
'open'
}
)
self
.
assertEqual
(
response
.
status_code
,
status_code
)
self
.
assertTrue
(
'id'
in
response
.
data
)
# pylint: disable=E1103
self
.
assertEqual
(
response
.
data
[
'id'
],
group_id
)
# pylint: disable=E1103
@httpretty.activate
def
test_create_new_closed_group
(
self
):
group_id
=
'12345678'
status_code
=
200
self
.
set_facebook_interceptor_for_access_token
()
self
.
set_facebook_interceptor_for_groups
({
'id'
:
group_id
},
status_code
)
# Create new group
url
=
reverse
(
'create-delete-group'
,
kwargs
=
{
'group_id'
:
''
})
response
=
self
.
client
.
post
(
url
,
{
'name'
:
'TheBestGroup'
,
'description'
:
'The group for the best people'
,
'privacy'
:
'closed'
}
)
self
.
assertEqual
(
response
.
status_code
,
status_code
)
self
.
assertTrue
(
'id'
in
response
.
data
)
# pylint: disable=E1103
self
.
assertEqual
(
response
.
data
[
'id'
],
group_id
)
# pylint: disable=E1103
def
test_create_new_group_no_name
(
self
):
url
=
reverse
(
'create-delete-group'
,
kwargs
=
{
'group_id'
:
''
})
response
=
self
.
client
.
post
(
url
,
{})
self
.
assertEqual
(
response
.
status_code
,
400
)
def
test_create_new_group_with_invalid_name
(
self
):
url
=
reverse
(
'create-delete-group'
,
kwargs
=
{
'group_id'
:
''
})
response
=
self
.
client
.
post
(
url
,
{
'invalid_name'
:
'TheBestGroup'
})
self
.
assertEqual
(
response
.
status_code
,
400
)
def
test_create_new_group_with_invalid_privacy
(
self
):
url
=
reverse
(
'create-delete-group'
,
kwargs
=
{
'group_id'
:
''
})
response
=
self
.
client
.
post
(
url
,
{
'name'
:
'TheBestGroup'
,
'privacy'
:
'half_open_half_closed'
}
)
self
.
assertEqual
(
response
.
status_code
,
400
)
@httpretty.activate
def
test_delete_group_that_exists
(
self
):
# Create new group
group_id
=
'12345678'
status_code
=
200
self
.
set_facebook_interceptor_for_access_token
()
self
.
set_facebook_interceptor_for_groups
({
'id'
:
group_id
},
status_code
)
url
=
reverse
(
'create-delete-group'
,
kwargs
=
{
'group_id'
:
''
})
response
=
self
.
client
.
post
(
url
,
{
'name'
:
'TheBestGroup'
,
'description'
:
'The group for the best people'
,
'privacy'
:
'open'
}
)
self
.
assertEqual
(
response
.
status_code
,
status_code
)
self
.
assertTrue
(
'id'
in
response
.
data
)
# pylint: disable=E1103
# delete group
httpretty
.
register_uri
(
httpretty
.
POST
,
'https://graph.facebook.com/{}/{}/groups/{}?access_token=FakeToken&method=delete'
.
format
(
settings
.
FACEBOOK_API_VERSION
,
settings
.
FACEBOOK_APP_ID
,
group_id
),
body
=
'{"success": "true"}'
,
status
=
status_code
)
response
=
self
.
delete_group
(
response
.
data
[
'id'
])
# pylint: disable=E1101
self
.
assertTrue
(
response
.
status_code
,
status_code
)
@httpretty.activate
def
test_delete
(
self
):
group_id
=
'12345678'
status_code
=
400
httpretty
.
register_uri
(
httpretty
.
GET
,
'https://graph.facebook.com/oauth/access_token?client_secret={}&grant_type=client_credentials&client_id={}'
.
format
(
settings
.
FACEBOOK_APP_SECRET
,
settings
.
FACEBOOK_APP_ID
),
body
=
'FakeToken=FakeToken'
,
status
=
200
)
httpretty
.
register_uri
(
httpretty
.
POST
,
'https://graph.facebook.com/{}/{}/groups/{}?access_token=FakeToken&method=delete'
.
format
(
settings
.
FACEBOOK_API_VERSION
,
settings
.
FACEBOOK_APP_ID
,
group_id
),
body
=
'{"error": {"message": "error message"}}'
,
status
=
status_code
)
response
=
self
.
delete_group
(
group_id
)
self
.
assertTrue
(
response
.
status_code
,
status_code
)
# Member addition and Removal tests
@data
(
'1234,,,,5678,,'
,
'this00is00not00a00valid00id'
,
'1234,abc,5678'
,
''
)
def
test_invite_single_member_malformed_member_id
(
self
,
member_id
):
group_id
=
'111111111111111'
response
=
self
.
invite_to_group
(
group_id
,
member_id
)
self
.
assertEqual
(
response
.
status_code
,
400
)
@httpretty.activate
def
test_invite_single_member
(
self
):
group_id
=
'111111111111111'
member_id
=
'44444444444444444'
status_code
=
200
self
.
set_facebook_interceptor_for_access_token
()
self
.
set_facebook_interceptor_for_members
({
'success'
:
'True'
},
status_code
,
group_id
,
member_id
)
response
=
self
.
invite_to_group
(
group_id
,
member_id
)
self
.
assertEqual
(
response
.
status_code
,
status_code
)
self
.
assertTrue
(
'success'
in
response
.
data
[
member_id
])
@httpretty.activate
def
test_invite_multiple_members_successfully
(
self
):
member_ids
=
'222222222222222,333333333333333,44444444444444444'
group_id
=
'111111111111111'
status_code
=
200
self
.
set_facebook_interceptor_for_access_token
()
for
member_id
in
member_ids
.
split
(
','
):
self
.
set_facebook_interceptor_for_members
({
'success'
:
'True'
},
status_code
,
group_id
,
member_id
)
response
=
self
.
invite_to_group
(
group_id
,
member_ids
)
self
.
assertEqual
(
response
.
status_code
,
status_code
)
for
member_id
in
member_ids
.
split
(
','
):
self
.
assertTrue
(
'success'
in
response
.
data
[
member_id
])
@httpretty.activate
def
test_invite_single_member_unsuccessfully
(
self
):
group_id
=
'111111111111111'
member_id
=
'44444444444444444'
status_code
=
400
self
.
set_facebook_interceptor_for_access_token
()
self
.
set_facebook_interceptor_for_members
(
{
'error'
:
{
'message'
:
'error message'
}},
status_code
,
group_id
,
member_id
)
response
=
self
.
invite_to_group
(
group_id
,
member_id
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertTrue
(
'error message'
in
response
.
data
[
member_id
])
@httpretty.activate
def
test_invite_multiple_members_unsuccessfully
(
self
):
member_ids
=
'222222222222222,333333333333333,44444444444444444'
group_id
=
'111111111111111'
status_code
=
400
self
.
set_facebook_interceptor_for_access_token
()
for
member_id
in
member_ids
.
split
(
','
):
self
.
set_facebook_interceptor_for_members
(
{
'error'
:
{
'message'
:
'error message'
}},
status_code
,
group_id
,
member_id
)
response
=
self
.
invite_to_group
(
group_id
,
member_ids
)
self
.
assertEqual
(
response
.
status_code
,
200
)
for
member_id
in
member_ids
.
split
(
','
):
self
.
assertTrue
(
'error message'
in
response
.
data
[
member_id
])
lms/djangoapps/mobile_api/social_facebook/groups/urls.py
deleted
100644 → 0
View file @
8f7c158f
"""
URLs for groups API
"""
from
django.conf.urls
import
patterns
,
url
from
.views
import
Groups
,
GroupsMembers
urlpatterns
=
patterns
(
'mobile_api.social_facebook.groups.views'
,
url
(
r'^(?P<group_id>[\d]*)$'
,
Groups
.
as_view
(),
name
=
'create-delete-group'
),
url
(
r'^(?P<group_id>[\d]+)/member/(?P<member_id>[\d]*,*)$'
,
GroupsMembers
.
as_view
(),
name
=
'add-remove-member'
)
)
lms/djangoapps/mobile_api/social_facebook/groups/views.py
deleted
100644 → 0
View file @
8f7c158f
"""
Views for groups info API
"""
from
rest_framework
import
generics
,
status
,
mixins
from
rest_framework.response
import
Response
from
django.conf
import
settings
import
facebook
from
...utils
import
mobile_view
from
.
import
serializers
@mobile_view
()
class
Groups
(
generics
.
CreateAPIView
,
mixins
.
DestroyModelMixin
):
"""
**Use Case**
An API to Create or Delete course groups.
Note: The Delete is not invoked from the current version of the app
and is used only for testing with facebook dependencies.
**Creation Example request**:
POST /api/mobile/v0.5/social/facebook/groups/
Parameters: name : string,
description : string,
privacy : open/closed
**Creation Response Values**
{"id": group_id}
**Deletion Example request**:
DELETE /api/mobile/v0.5/social/facebook/groups/<group_id>
**Deletion Response Values**
{"success" : "true"}
"""
serializer_class
=
serializers
.
GroupSerializer
def
create
(
self
,
request
,
*
args
,
**
kwargs
):
serializer
=
self
.
get_serializer
(
data
=
request
.
data
)
if
not
serializer
.
is_valid
():
return
Response
(
serializer
.
errors
,
status
=
status
.
HTTP_400_BAD_REQUEST
)
try
:
app_groups_response
=
facebook_graph_api
()
.
request
(
settings
.
FACEBOOK_API_VERSION
+
'/'
+
settings
.
FACEBOOK_APP_ID
+
"/groups"
,
post_args
=
request
.
POST
.
dict
()
)
return
Response
(
app_groups_response
)
except
facebook
.
GraphAPIError
,
ex
:
return
Response
({
'error'
:
ex
.
result
[
'error'
][
'message'
]},
status
=
status
.
HTTP_400_BAD_REQUEST
)
def
delete
(
self
,
request
,
*
args
,
**
kwargs
):
# pylint: disable=unused-argument
"""
Deletes the course group.
"""
try
:
return
Response
(
facebook_graph_api
()
.
request
(
settings
.
FACEBOOK_API_VERSION
+
'/'
+
settings
.
FACEBOOK_APP_ID
+
"/groups/"
+
kwargs
[
'group_id'
],
post_args
=
{
'method'
:
'delete'
}
)
)
except
facebook
.
GraphAPIError
,
ex
:
return
Response
({
'error'
:
ex
.
result
[
'error'
][
'message'
]},
status
=
status
.
HTTP_400_BAD_REQUEST
)
@mobile_view
()
class
GroupsMembers
(
generics
.
CreateAPIView
,
mixins
.
DestroyModelMixin
):
"""
**Use Case**
An API to Invite and Remove members to a group
Note: The Remove is not invoked from the current version
of the app and is used only for testing with facebook dependencies.
**Invite Example request**:
POST /api/mobile/v0.5/social/facebook/groups/<group_id>/member/
Parameters: members : int,int,int...
**Invite Response Values**
{"member_id" : success/error_message}
A response with each member_id and whether or not the member was added successfully.
If the member was not added successfully the Facebook error message is provided.
**Remove Example request**:
DELETE /api/mobile/v0.5/social/facebook/groups/<group_id>/member/<member_id>
**Remove Response Values**
{"success" : "true"}
"""
serializer_class
=
serializers
.
GroupsMembersSerializer
def
create
(
self
,
request
,
*
args
,
**
kwargs
):
serializer
=
self
.
get_serializer
(
data
=
request
.
data
)
if
not
serializer
.
is_valid
():
return
Response
(
serializer
.
errors
,
status
=
status
.
HTTP_400_BAD_REQUEST
)
graph
=
facebook_graph_api
()
url
=
settings
.
FACEBOOK_API_VERSION
+
'/'
+
kwargs
[
'group_id'
]
+
"/members"
member_ids
=
serializer
.
data
[
'member_ids'
]
.
split
(
','
)
response
=
{}
for
member_id
in
member_ids
:
try
:
if
'success'
in
graph
.
request
(
url
,
post_args
=
{
'member'
:
member_id
}):
response
[
member_id
]
=
'success'
except
facebook
.
GraphAPIError
,
ex
:
response
[
member_id
]
=
ex
.
result
[
'error'
][
'message'
]
return
Response
(
response
,
status
=
status
.
HTTP_200_OK
)
def
delete
(
self
,
request
,
*
args
,
**
kwargs
):
# pylint: disable=unused-argument
"""
Deletes the member from the course group.
"""
try
:
return
Response
(
facebook_graph_api
()
.
request
(
settings
.
FACEBOOK_API_VERSION
+
'/'
+
kwargs
[
'group_id'
]
+
"/members"
,
post_args
=
{
'method'
:
'delete'
,
'member'
:
kwargs
[
'member_id'
]}
)
)
except
facebook
.
GraphAPIError
,
ex
:
return
Response
({
'error'
:
ex
.
result
[
'error'
][
'message'
]},
status
=
status
.
HTTP_400_BAD_REQUEST
)
def
facebook_graph_api
():
"""
Returns the result from calling Facebook's Graph API with the app's access token.
"""
return
facebook
.
GraphAPI
(
facebook
.
get_app_access_token
(
settings
.
FACEBOOK_APP_ID
,
settings
.
FACEBOOK_APP_SECRET
))
lms/djangoapps/mobile_api/social_facebook/models.py
deleted
100644 → 0
View file @
8f7c158f
"""
A models.py is required to make this an app (until we move to Django 1.7)
"""
lms/djangoapps/mobile_api/social_facebook/preferences/__init__.py
deleted
100644 → 0
View file @
8f7c158f
"""
Users Sharing preferences API
"""
lms/djangoapps/mobile_api/social_facebook/preferences/models.py
deleted
100644 → 0
View file @
8f7c158f
"""
A models.py is required to make this an app (until we move to Django 1.7)
"""
lms/djangoapps/mobile_api/social_facebook/preferences/serializers.py
deleted
100644 → 0
View file @
8f7c158f
"""
Serializer for Share Settings API
"""
from
rest_framework
import
serializers
class
UserSharingSerializar
(
serializers
.
Serializer
):
"""
Serializes user social settings
"""
share_with_facebook_friends
=
serializers
.
BooleanField
(
required
=
True
)
lms/djangoapps/mobile_api/social_facebook/preferences/tests.py
deleted
100644 → 0
View file @
8f7c158f
"""
Tests for users sharing preferences
"""
from
django.core.urlresolvers
import
reverse
from
..test_utils
import
SocialFacebookTestCase
class
StudentProfileViewTest
(
SocialFacebookTestCase
):
""" Tests for the student profile views. """
USERNAME
=
u'bnotions'
PASSWORD
=
u'horse'
EMAIL
=
u'horse@bnotions.com'
FULL_NAME
=
u'bnotions horse'
def
setUp
(
self
):
super
(
StudentProfileViewTest
,
self
)
.
setUp
()
self
.
user_create_and_signin
(
1
)
def
assert_shared_value
(
self
,
response
,
expected_value
=
'True'
):
"""
Tests whether the response is successful and whether the
share_with_facebook_friends value is set to the expected value.
"""
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertTrue
(
'share_with_facebook_friends'
in
response
.
data
)
self
.
assertTrue
(
expected_value
in
response
.
data
[
'share_with_facebook_friends'
])
def
test_set_preferences_to_true
(
self
):
url
=
reverse
(
'preferences'
)
response
=
self
.
client
.
post
(
url
,
{
'share_with_facebook_friends'
:
'True'
})
self
.
assert_shared_value
(
response
)
def
test_set_preferences_to_false
(
self
):
url
=
reverse
(
'preferences'
)
response
=
self
.
client
.
post
(
url
,
{
'share_with_facebook_friends'
:
'False'
})
self
.
assert_shared_value
(
response
,
'False'
)
def
test_set_preferences_no_parameters
(
self
):
# Note that if no value is given it will default to False
url
=
reverse
(
'preferences'
)
response
=
self
.
client
.
post
(
url
,
{})
self
.
assert_shared_value
(
response
,
'False'
)
def
test_set_preferences_invalid_parameters
(
self
):
# Note that if no value is given it will default to False
# also in the case of invalid parameters
url
=
reverse
(
'preferences'
)
response
=
self
.
client
.
post
(
url
,
{
'bad_param'
:
'False'
})
self
.
assert_shared_value
(
response
,
'False'
)
def
test_get_preferences_after_setting_them
(
self
):
url
=
reverse
(
'preferences'
)
for
boolean
in
[
'True'
,
'False'
]:
# Set the preference
response
=
self
.
client
.
post
(
url
,
{
'share_with_facebook_friends'
:
boolean
})
self
.
assert_shared_value
(
response
,
boolean
)
# Get the preference
response
=
self
.
client
.
get
(
url
)
self
.
assert_shared_value
(
response
,
boolean
)
def
test_get_preferences_without_setting_them
(
self
):
url
=
reverse
(
'preferences'
)
# Get the preference
response
=
self
.
client
.
get
(
url
)
self
.
assert_shared_value
(
response
,
'False'
)
lms/djangoapps/mobile_api/social_facebook/preferences/urls.py
deleted
100644 → 0
View file @
8f7c158f
"""
URLs for users sharing preferences
"""
from
django.conf.urls
import
patterns
,
url
from
.views
import
UserSharing
urlpatterns
=
patterns
(
'mobile_api.social_facebook.preferences.views'
,
url
(
r'^preferences/$'
,
UserSharing
.
as_view
(),
name
=
'preferences'
),
)
lms/djangoapps/mobile_api/social_facebook/preferences/views.py
deleted
100644 → 0
View file @
8f7c158f
"""
Views for users sharing preferences
"""
from
rest_framework
import
generics
,
status
from
rest_framework.response
import
Response
from
openedx.core.djangoapps.user_api.preferences.api
import
get_user_preferences
,
set_user_preference
from
...utils
import
mobile_view
from
.
import
serializers
@mobile_view
()
class
UserSharing
(
generics
.
ListCreateAPIView
):
"""
**Use Case**
An API to retrieve or update the users social sharing settings
**GET Example request**:
GET /api/mobile/v0.5/settings/preferences/
**GET Response Values**
{'share_with_facebook_friends': 'True'}
**POST Example request**:
POST /api/mobile/v0.5/settings/preferences/
paramters: share_with_facebook_friends : True
**POST Response Values**
{'share_with_facebook_friends': 'True'}
"""
serializer_class
=
serializers
.
UserSharingSerializar
def
create
(
self
,
request
,
*
args
,
**
kwargs
):
serializer
=
self
.
get_serializer
(
data
=
request
.
data
)
if
serializer
.
is_valid
():
value
=
serializer
.
data
[
'share_with_facebook_friends'
]
set_user_preference
(
request
.
user
,
"share_with_facebook_friends"
,
value
)
return
self
.
get
(
request
,
*
args
,
**
kwargs
)
return
Response
(
serializer
.
errors
,
status
=
status
.
HTTP_400_BAD_REQUEST
)
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
preferences
=
get_user_preferences
(
request
.
user
)
response
=
{
'share_with_facebook_friends'
:
preferences
.
get
(
'share_with_facebook_friends'
,
'False'
)}
return
Response
(
response
)
lms/djangoapps/mobile_api/social_facebook/test_utils.py
deleted
100644 → 0
View file @
8f7c158f
"""
Test utils for Facebook functionality
"""
import
httpretty
import
json
from
rest_framework.test
import
APITestCase
from
django.conf
import
settings
from
django.core.urlresolvers
import
reverse
from
social.apps.django_app.default.models
import
UserSocialAuth
from
course_modes.models
import
CourseMode
from
student.models
import
CourseEnrollment
from
student.views
import
login_oauth_token
from
openedx.core.djangoapps.user_api.preferences.api
import
get_user_preference
,
set_user_preference
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
courseware.tests.factories
import
UserFactory
class
SocialFacebookTestCase
(
ModuleStoreTestCase
,
APITestCase
):
"""
Base Class for social test cases
"""
USERS
=
{
1
:
{
'USERNAME'
:
"TestUser One"
,
'EMAIL'
:
"test_one@ebnotions.com"
,
'PASSWORD'
:
"edx"
,
'FB_ID'
:
"11111111111111111"
},
2
:
{
'USERNAME'
:
"TestUser Two"
,
'EMAIL'
:
"test_two@ebnotions.com"
,
'PASSWORD'
:
"edx"
,
'FB_ID'
:
"22222222222222222"
},
3
:
{
'USERNAME'
:
"TestUser Three"
,
'EMAIL'
:
"test_three@ebnotions.com"
,
'PASSWORD'
:
"edx"
,
'FB_ID'
:
"33333333333333333"
}
}
BACKEND
=
"facebook"
USER_URL
=
"https://graph.facebook.com/me"
UID_FIELD
=
"id"
_FB_USER_ACCESS_TOKEN
=
'ThisIsAFakeFacebookToken'
users
=
{}
def
setUp
(
self
):
super
(
SocialFacebookTestCase
,
self
)
.
setUp
()
def
set_facebook_interceptor_for_access_token
(
self
):
"""
Facebook interceptor for groups access_token
"""
httpretty
.
register_uri
(
httpretty
.
GET
,
'https://graph.facebook.com/oauth/access_token?client_secret='
+
settings
.
FACEBOOK_APP_SECRET
+
'&grant_type=client_credentials&client_id='
+
settings
.
FACEBOOK_APP_ID
,
body
=
'FakeToken=FakeToken'
,
status
=
200
)
def
set_facebook_interceptor_for_groups
(
self
,
data
,
status
):
"""
Facebook interceptor for groups test
"""
httpretty
.
register_uri
(
httpretty
.
POST
,
'https://graph.facebook.com/'
+
settings
.
FACEBOOK_API_VERSION
+
'/'
+
settings
.
FACEBOOK_APP_ID
+
'/groups'
,
body
=
json
.
dumps
(
data
),
status
=
status
)
def
set_facebook_interceptor_for_members
(
self
,
data
,
status
,
group_id
,
member_id
):
"""
Facebook interceptor for group members tests
"""
httpretty
.
register_uri
(
httpretty
.
POST
,
'https://graph.facebook.com/'
+
settings
.
FACEBOOK_API_VERSION
+
'/'
+
group_id
+
'/members?member='
+
member_id
+
'&access_token=FakeToken'
,
body
=
json
.
dumps
(
data
),
status
=
status
)
def
set_facebook_interceptor_for_friends
(
self
,
data
):
"""
Facebook interceptor for friends tests
"""
httpretty
.
register_uri
(
httpretty
.
GET
,
"https://graph.facebook.com/v2.2/me/friends"
,
body
=
json
.
dumps
(
data
),
status
=
201
)
def
delete_group
(
self
,
group_id
):
"""
Invoke the delete groups view
"""
url
=
reverse
(
'create-delete-group'
,
kwargs
=
{
'group_id'
:
group_id
})
response
=
self
.
client
.
delete
(
url
)
return
response
def
invite_to_group
(
self
,
group_id
,
member_ids
):
"""
Invoke the invite to group view
"""
url
=
reverse
(
'add-remove-member'
,
kwargs
=
{
'group_id'
:
group_id
,
'member_id'
:
''
})
return
self
.
client
.
post
(
url
,
{
'member_ids'
:
member_ids
})
def
remove_from_group
(
self
,
group_id
,
member_id
):
"""
Invoke the remove from group view
"""
url
=
reverse
(
'add-remove-member'
,
kwargs
=
{
'group_id'
:
group_id
,
'member_id'
:
member_id
})
response
=
self
.
client
.
delete
(
url
)
self
.
assertEqual
(
response
.
status_code
,
200
)
def
link_edx_account_to_social
(
self
,
user
,
backend
,
social_uid
):
"""
Register the user to the social auth backend
"""
reverse
(
login_oauth_token
,
kwargs
=
{
"backend"
:
backend
})
UserSocialAuth
.
objects
.
create
(
user
=
user
,
provider
=
backend
,
uid
=
social_uid
)
def
set_sharing_preferences
(
self
,
user
,
boolean_value
):
"""
Sets self.user's share settings to boolean_value
"""
# Note that setting the value to boolean will result in the conversion to the unicode form of the boolean.
set_user_preference
(
user
,
'share_with_facebook_friends'
,
boolean_value
)
self
.
assertEqual
(
get_user_preference
(
user
,
'share_with_facebook_friends'
),
unicode
(
boolean_value
))
def
_change_enrollment
(
self
,
action
,
course_id
=
None
,
email_opt_in
=
None
):
"""
Change the student's enrollment status in a course.
Args:
action (string): The action to perform (either "enroll" or "unenroll")
Keyword Args:
course_id (unicode): If provided, use this course ID. Otherwise, use the
course ID created in the setup for this test.
email_opt_in (unicode): If provided, pass this value along as
an additional GET parameter.
"""
if
course_id
is
None
:
course_id
=
unicode
(
self
.
course
.
id
)
params
=
{
'enrollment_action'
:
action
,
'course_id'
:
course_id
}
if
email_opt_in
:
params
[
'email_opt_in'
]
=
email_opt_in
return
self
.
client
.
post
(
reverse
(
'change_enrollment'
),
params
)
def
user_create_and_signin
(
self
,
user_number
):
"""
Create a user and sign them in
"""
self
.
users
[
user_number
]
=
UserFactory
.
create
(
username
=
self
.
USERS
[
user_number
][
'USERNAME'
],
email
=
self
.
USERS
[
user_number
][
'EMAIL'
],
password
=
self
.
USERS
[
user_number
][
'PASSWORD'
]
)
self
.
client
.
login
(
username
=
self
.
USERS
[
user_number
][
'USERNAME'
],
password
=
self
.
USERS
[
user_number
][
'PASSWORD'
])
def
enroll_in_course
(
self
,
user
,
course
):
"""
Enroll a user in the course
"""
resp
=
self
.
_change_enrollment
(
'enroll'
,
course_id
=
course
.
id
)
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
assertTrue
(
CourseEnrollment
.
is_enrolled
(
user
,
course
.
id
))
course_mode
,
is_active
=
CourseEnrollment
.
enrollment_mode_for_user
(
user
,
course
.
id
)
self
.
assertTrue
(
is_active
)
self
.
assertEqual
(
course_mode
,
CourseMode
.
DEFAULT_MODE_SLUG
)
lms/djangoapps/mobile_api/social_facebook/urls.py
deleted
100644 → 0
View file @
8f7c158f
"""
URLs for Social Facebook
"""
from
django.conf.urls
import
patterns
,
url
,
include
urlpatterns
=
patterns
(
''
,
url
(
r'^courses/'
,
include
(
'mobile_api.social_facebook.courses.urls'
)),
url
(
r'^friends/'
,
include
(
'mobile_api.social_facebook.friends.urls'
)),
url
(
r'^groups/'
,
include
(
'mobile_api.social_facebook.groups.urls'
)),
)
lms/djangoapps/mobile_api/social_facebook/utils.py
deleted
100644 → 0
View file @
8f7c158f
"""
Common utility methods and decorators for Social Facebook APIs.
"""
import
json
import
urllib2
import
facebook
from
django.conf
import
settings
from
django.core.exceptions
import
ObjectDoesNotExist
from
rest_framework
import
status
from
rest_framework.response
import
Response
from
social.apps.django_app.default.models
import
UserSocialAuth
from
openedx.core.djangoapps.user_api.models
import
UserPreference
from
student.models
import
User
# TODO
# The pagination strategy needs to be further flushed out.
# What is the default page size for the facebook Graph API? 25? Is the page size a parameter that can be tweaked?
# If a user has a large number of friends, we would be calling the FB API num_friends/page_size times.
#
# However, on the app, we don't plan to display all those friends anyway.
# If we do, for scalability, the endpoints themselves would need to be paginated.
def
get_pagination
(
friends
):
"""
Get paginated data from FaceBook response
"""
data
=
friends
[
'data'
]
while
'paging'
in
friends
and
'next'
in
friends
[
'paging'
]:
response
=
urllib2
.
urlopen
(
friends
[
'paging'
][
'next'
])
friends
=
json
.
loads
(
response
.
read
())
data
=
data
+
friends
[
'data'
]
return
data
def
get_friends_from_facebook
(
serializer
):
"""
Return a list with the result of a facebook /me/friends call
using the oauth_token contained within the serializer object.
If facebook returns an error, return a response object containing
the error message.
"""
try
:
graph
=
facebook
.
GraphAPI
(
serializer
.
data
[
'oauth_token'
])
friends
=
graph
.
request
(
settings
.
FACEBOOK_API_VERSION
+
"/me/friends"
)
return
get_pagination
(
friends
)
except
facebook
.
GraphAPIError
,
ex
:
return
Response
({
'error'
:
ex
.
result
[
'error'
][
'message'
]},
status
=
status
.
HTTP_400_BAD_REQUEST
)
def
get_linked_edx_accounts
(
data
):
"""
Return a list of friends from the input that are edx users with the
additional attributes of edX_id and edX_username
"""
friends_that_are_edx_users
=
[]
for
friend
in
data
:
query_set
=
UserSocialAuth
.
objects
.
filter
(
uid
=
unicode
(
friend
[
'id'
]))
if
query_set
.
count
()
==
1
:
friend
[
'edX_id'
]
=
query_set
[
0
]
.
user_id
friend
[
'edX_username'
]
=
query_set
[
0
]
.
user
.
username
friends_that_are_edx_users
.
append
(
friend
)
return
friends_that_are_edx_users
def
share_with_facebook_friends
(
friend
):
"""
Return true if the user's share_with_facebook_friends preference is set to true.
"""
# Calling UserPreference directly because the requesting user may be different (and not is_staff).
try
:
existing_user
=
User
.
objects
.
get
(
username
=
friend
[
'edX_username'
])
except
ObjectDoesNotExist
:
return
False
return
UserPreference
.
get_value
(
existing_user
,
'share_with_facebook_friends'
)
==
'True'
lms/djangoapps/mobile_api/urls.py
View file @
4bbe9a20
"""
"""
URLs for mobile API
URLs for mobile API
"""
"""
from
django.conf
import
settings
from
django.conf.urls
import
patterns
,
url
,
include
from
django.conf.urls
import
patterns
,
url
,
include
from
.users.views
import
my_user_info
from
.users.views
import
my_user_info
...
@@ -13,9 +12,3 @@ urlpatterns = patterns(
...
@@ -13,9 +12,3 @@ urlpatterns = patterns(
url
(
r'^video_outlines/'
,
include
(
'mobile_api.video_outlines.urls'
)),
url
(
r'^video_outlines/'
,
include
(
'mobile_api.video_outlines.urls'
)),
url
(
r'^course_info/'
,
include
(
'mobile_api.course_info.urls'
)),
url
(
r'^course_info/'
,
include
(
'mobile_api.course_info.urls'
)),
)
)
if
settings
.
FEATURES
[
"ENABLE_MOBILE_SOCIAL_FACEBOOK_FEATURES"
]:
urlpatterns
+=
(
url
(
r'^social/facebook/'
,
include
(
'mobile_api.social_facebook.urls'
)),
url
(
r'^settings/'
,
include
(
'mobile_api.social_facebook.preferences.urls'
)),
)
lms/djangoapps/mobile_api/users/serializers.py
View file @
4bbe9a20
...
@@ -76,14 +76,6 @@ class CourseOverviewField(serializers.RelatedField):
...
@@ -76,14 +76,6 @@ class CourseOverviewField(serializers.RelatedField):
kwargs
=
{
'course_id'
:
course_id
},
kwargs
=
{
'course_id'
:
course_id
},
request
=
request
,
request
=
request
,
),
),
# Note: The following 2 should be deprecated.
'social_urls'
:
{
'facebook'
:
course_overview
.
facebook_url
,
},
'latest_updates'
:
{
'video'
:
None
},
}
}
...
...
lms/djangoapps/mobile_api/users/tests.py
View file @
4bbe9a20
...
@@ -253,23 +253,6 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest
...
@@ -253,23 +253,6 @@ class TestUserEnrollmentApi(UrlResetMixin, MobileAPITestCase, MobileAuthUserTest
)
)
)
)
def
test_no_facebook_url
(
self
):
self
.
login_and_enroll
()
response
=
self
.
api_response
()
course_data
=
response
.
data
[
0
][
'course'
]
self
.
assertIsNone
(
course_data
[
'social_urls'
][
'facebook'
])
def
test_facebook_url
(
self
):
self
.
login_and_enroll
()
self
.
course
.
facebook_url
=
"http://facebook.com/test_group_page"
self
.
store
.
update_item
(
self
.
course
,
self
.
user
.
id
)
response
=
self
.
api_response
()
course_data
=
response
.
data
[
0
][
'course'
]
self
.
assertEquals
(
course_data
[
'social_urls'
][
'facebook'
],
self
.
course
.
facebook_url
)
@patch.dict
(
"django.conf.settings.FEATURES"
,
{
"ENABLE_DISCUSSION_SERVICE"
:
True
})
@patch.dict
(
"django.conf.settings.FEATURES"
,
{
"ENABLE_DISCUSSION_SERVICE"
:
True
})
def
test_discussion_url
(
self
):
def
test_discussion_url
(
self
):
self
.
login_and_enroll
()
self
.
login_and_enroll
()
...
...
lms/djangoapps/mobile_api/users/views.py
View file @
4bbe9a20
...
@@ -236,7 +236,6 @@ class UserCourseEnrollmentsList(generics.ListAPIView):
...
@@ -236,7 +236,6 @@ class UserCourseEnrollmentsList(generics.ListAPIView):
it is enabled, otherwise null.
it is enabled, otherwise null.
* end: The end date of the course.
* end: The end date of the course.
* id: The unique ID of the course.
* id: The unique ID of the course.
* latest_updates: Reserved for future use.
* name: The name of the course.
* name: The name of the course.
* number: The course number.
* number: The course number.
* org: The organization that created the course.
* org: The organization that created the course.
...
...
lms/envs/common.py
View file @
4bbe9a20
...
@@ -271,10 +271,6 @@ FEATURES = {
...
@@ -271,10 +271,6 @@ FEATURES = {
# Expose Mobile REST API. Note that if you use this, you must also set
# Expose Mobile REST API. Note that if you use this, you must also set
# ENABLE_OAUTH2_PROVIDER to True
# ENABLE_OAUTH2_PROVIDER to True
'ENABLE_MOBILE_REST_API'
:
False
,
'ENABLE_MOBILE_REST_API'
:
False
,
'ENABLE_MOBILE_SOCIAL_FACEBOOK_FEATURES'
:
False
,
# Enable temporary APIs required for xBlocks on Mobile
'ENABLE_COURSE_BLOCKS_NAVIGATION_API'
:
False
,
# Enable the combined login/registration form
# Enable the combined login/registration form
'ENABLE_COMBINED_LOGIN_REGISTRATION'
:
False
,
'ENABLE_COMBINED_LOGIN_REGISTRATION'
:
False
,
...
...
lms/envs/test.py
View file @
4bbe9a20
...
@@ -293,9 +293,7 @@ OIDC_COURSE_HANDLER_CACHE_TIMEOUT = 0
...
@@ -293,9 +293,7 @@ OIDC_COURSE_HANDLER_CACHE_TIMEOUT = 0
########################### External REST APIs #################################
########################### External REST APIs #################################
FEATURES
[
'ENABLE_MOBILE_REST_API'
]
=
True
FEATURES
[
'ENABLE_MOBILE_REST_API'
]
=
True
FEATURES
[
'ENABLE_MOBILE_SOCIAL_FACEBOOK_FEATURES'
]
=
True
FEATURES
[
'ENABLE_VIDEO_ABSTRACTION_LAYER_API'
]
=
True
FEATURES
[
'ENABLE_VIDEO_ABSTRACTION_LAYER_API'
]
=
True
FEATURES
[
'ENABLE_COURSE_BLOCKS_NAVIGATION_API'
]
=
True
###################### Payment ##############################3
###################### Payment ##############################3
# Enable fake payment processing page
# Enable fake payment processing page
...
...
openedx/core/djangoapps/content/course_overviews/migrations/0008_remove_courseoverview_facebook_url.py
0 → 100644
View file @
4bbe9a20
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
migrations
,
models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'course_overviews'
,
'0007_courseoverviewimageconfig'
),
]
operations
=
[
migrations
.
RemoveField
(
model_name
=
'courseoverview'
,
name
=
'facebook_url'
,
),
]
openedx/core/djangoapps/content/course_overviews/models.py
View file @
4bbe9a20
...
@@ -66,7 +66,6 @@ class CourseOverview(TimeStampedModel):
...
@@ -66,7 +66,6 @@ class CourseOverview(TimeStampedModel):
# URLs
# URLs
course_image_url
=
TextField
()
course_image_url
=
TextField
()
facebook_url
=
TextField
(
null
=
True
)
social_sharing_url
=
TextField
(
null
=
True
)
social_sharing_url
=
TextField
(
null
=
True
)
end_of_course_survey_url
=
TextField
(
null
=
True
)
end_of_course_survey_url
=
TextField
(
null
=
True
)
...
@@ -156,7 +155,6 @@ class CourseOverview(TimeStampedModel):
...
@@ -156,7 +155,6 @@ class CourseOverview(TimeStampedModel):
announcement
=
course
.
announcement
,
announcement
=
course
.
announcement
,
course_image_url
=
course_image_url
(
course
),
course_image_url
=
course_image_url
(
course
),
facebook_url
=
course
.
facebook_url
,
social_sharing_url
=
course
.
social_sharing_url
,
social_sharing_url
=
course
.
social_sharing_url
,
certificates_display_behavior
=
course
.
certificates_display_behavior
,
certificates_display_behavior
=
course
.
certificates_display_behavior
,
...
...
openedx/core/djangoapps/content/course_overviews/tests.py
View file @
4bbe9a20
...
@@ -91,7 +91,6 @@ class CourseOverviewTestCase(ModuleStoreTestCase):
...
@@ -91,7 +91,6 @@ class CourseOverviewTestCase(ModuleStoreTestCase):
'display_number_with_default'
,
'display_number_with_default'
,
'display_org_with_default'
,
'display_org_with_default'
,
'advertised_start'
,
'advertised_start'
,
'facebook_url'
,
'social_sharing_url'
,
'social_sharing_url'
,
'certificates_display_behavior'
,
'certificates_display_behavior'
,
'certificates_show_before_end'
,
'certificates_show_before_end'
,
...
...
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