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
818d59e8
Commit
818d59e8
authored
Mar 03, 2015
by
Nimisha Asthagiri
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #7197 from edx/bnotions/social-features
Bnotions/social features
parents
95387be9
c375f666
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
34 changed files
with
1223 additions
and
1 deletions
+1223
-1
lms/djangoapps/mobile_api/social_facebook/__init__.py
+42
-0
lms/djangoapps/mobile_api/social_facebook/courses/__init__.py
+3
-0
lms/djangoapps/mobile_api/social_facebook/courses/models.py
+3
-0
lms/djangoapps/mobile_api/social_facebook/courses/serializers.py
+11
-0
lms/djangoapps/mobile_api/social_facebook/courses/tests.py
+147
-0
lms/djangoapps/mobile_api/social_facebook/courses/urls.py
+15
-0
lms/djangoapps/mobile_api/social_facebook/courses/views.py
+64
-0
lms/djangoapps/mobile_api/social_facebook/friends/__init__.py
+3
-0
lms/djangoapps/mobile_api/social_facebook/friends/models.py
+3
-0
lms/djangoapps/mobile_api/social_facebook/friends/serializers.py
+11
-0
lms/djangoapps/mobile_api/social_facebook/friends/tests.py
+0
-0
lms/djangoapps/mobile_api/social_facebook/friends/urls.py
+16
-0
lms/djangoapps/mobile_api/social_facebook/friends/views.py
+71
-0
lms/djangoapps/mobile_api/social_facebook/groups/__init__.py
+3
-0
lms/djangoapps/mobile_api/social_facebook/groups/models.py
+3
-0
lms/djangoapps/mobile_api/social_facebook/groups/serializers.py
+30
-0
lms/djangoapps/mobile_api/social_facebook/groups/tests.py
+199
-0
lms/djangoapps/mobile_api/social_facebook/groups/urls.py
+20
-0
lms/djangoapps/mobile_api/social_facebook/groups/views.py
+143
-0
lms/djangoapps/mobile_api/social_facebook/models.py
+3
-0
lms/djangoapps/mobile_api/social_facebook/preferences/__init__.py
+3
-0
lms/djangoapps/mobile_api/social_facebook/preferences/models.py
+3
-0
lms/djangoapps/mobile_api/social_facebook/preferences/serializers.py
+11
-0
lms/djangoapps/mobile_api/social_facebook/preferences/tests.py
+68
-0
lms/djangoapps/mobile_api/social_facebook/preferences/urls.py
+14
-0
lms/djangoapps/mobile_api/social_facebook/preferences/views.py
+52
-0
lms/djangoapps/mobile_api/social_facebook/test_utils.py
+184
-0
lms/djangoapps/mobile_api/social_facebook/urls.py
+11
-0
lms/djangoapps/mobile_api/social_facebook/utils.py
+68
-0
lms/djangoapps/mobile_api/urls.py
+7
-1
lms/envs/aws.py
+5
-0
lms/envs/common.py
+1
-0
lms/envs/test.py
+5
-0
requirements/edx/base.txt
+1
-0
No files found.
lms/djangoapps/mobile_api/social_facebook/__init__.py
0 → 100644
View file @
818d59e8
"""
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
0 → 100644
View file @
818d59e8
"""
Courses API
"""
lms/djangoapps/mobile_api/social_facebook/courses/models.py
0 → 100644
View file @
818d59e8
"""
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
0 → 100644
View file @
818d59e8
"""
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
0 → 100644
View file @
818d59e8
# 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
0 → 100644
View file @
818d59e8
"""
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
0 → 100644
View file @
818d59e8
"""
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
,
files
=
request
.
FILES
)
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
type
(
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
)
]
return
Response
(
CourseEnrollmentSerializer
(
courses
,
context
=
{
'request'
:
request
})
.
data
)
lms/djangoapps/mobile_api/social_facebook/friends/__init__.py
0 → 100644
View file @
818d59e8
"""
Friends API
"""
lms/djangoapps/mobile_api/social_facebook/friends/models.py
0 → 100644
View file @
818d59e8
"""
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
0 → 100644
View file @
818d59e8
"""
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
0 → 100644
View file @
818d59e8
This diff is collapsed.
Click to expand it.
lms/djangoapps/mobile_api/social_facebook/friends/urls.py
0 → 100644
View file @
818d59e8
"""
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
0 → 100644
View file @
818d59e8
"""
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
,
files
=
request
.
FILES
)
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
type
(
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
0 → 100644
View file @
818d59e8
"""
Groups API
"""
lms/djangoapps/mobile_api/social_facebook/groups/models.py
0 → 100644
View file @
818d59e8
"""
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
0 → 100644
View file @
818d59e8
"""
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
0 → 100644
View file @
818d59e8
"""
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
)
# pylint: disable=E1101
@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
])
# pylint: disable=E1103
@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
])
# pylint: disable=E1103
@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
])
# pylint: disable=E1103
@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
])
# pylint: disable=E1103
lms/djangoapps/mobile_api/social_facebook/groups/urls.py
0 → 100644
View file @
818d59e8
"""
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
0 → 100644
View file @
818d59e8
"""
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
,
files
=
request
.
FILES
)
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
,
files
=
request
.
FILES
)
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
.
object
[
'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
0 → 100644
View file @
818d59e8
"""
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
0 → 100644
View file @
818d59e8
"""
Users Sharing preferences API
"""
lms/djangoapps/mobile_api/social_facebook/preferences/models.py
0 → 100644
View file @
818d59e8
"""
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
0 → 100644
View file @
818d59e8
"""
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
,
default
=
False
)
lms/djangoapps/mobile_api/social_facebook/preferences/tests.py
0 → 100644
View file @
818d59e8
# pylint: disable=no-member
"""
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
0 → 100644
View file @
818d59e8
"""
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
0 → 100644
View file @
818d59e8
"""
Views for users sharing preferences
"""
from
rest_framework
import
generics
,
status
from
rest_framework.response
import
Response
from
openedx.core.djangoapps.user_api.api.profile
import
preference_info
,
update_preferences
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
,
files
=
request
.
FILES
)
if
serializer
.
is_valid
():
value
=
serializer
.
object
[
'share_with_facebook_friends'
]
update_preferences
(
request
.
user
.
username
,
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
=
preference_info
(
request
.
user
.
username
)
response
=
{
'share_with_facebook_friends'
:
preferences
.
get
(
'share_with_facebook_friends'
,
'False'
)}
return
Response
(
response
)
lms/djangoapps/mobile_api/social_facebook/test_utils.py
0 → 100644
View file @
818d59e8
"""
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
student.models
import
CourseEnrollment
from
student.views
import
login_oauth_token
from
openedx.core.djangoapps.user_api.api.profile
import
preference_info
,
update_preferences
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
"""
update_preferences
(
user
.
username
,
share_with_facebook_friends
=
boolean_value
)
self
.
assertEqual
(
preference_info
(
user
.
username
)[
'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
,
'honor'
)
lms/djangoapps/mobile_api/social_facebook/urls.py
0 → 100644
View file @
818d59e8
"""
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
0 → 100644
View file @
818d59e8
"""
Common utility methods and decorators for Social Facebook APIs.
"""
import
json
import
urllib2
import
facebook
from
django.conf
import
settings
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.api.profile
import
preference_info
# 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
.
object
[
'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.
"""
share_fb_friends_settings
=
preference_info
(
friend
[
'edX_username'
])
return
share_fb_friends_settings
.
get
(
'share_with_facebook_friends'
,
None
)
==
'True'
lms/djangoapps/mobile_api/urls.py
View file @
818d59e8
"""
URLs for mobile API
"""
from
django.conf
import
settings
from
django.conf.urls
import
patterns
,
url
,
include
from
.users.views
import
my_user_info
# Additionally, we include login URLs for the browseable API.
urlpatterns
=
patterns
(
''
,
url
(
r'^users/'
,
include
(
'mobile_api.users.urls'
)),
...
...
@@ -13,3 +13,9 @@ urlpatterns = patterns(
url
(
r'^video_outlines/'
,
include
(
'mobile_api.video_outlines.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/envs/aws.py
View file @
818d59e8
...
...
@@ -517,3 +517,8 @@ PDF_RECEIPT_COBRAND_LOGO_HEIGHT_MM = ENV_TOKENS.get(
if
FEATURES
.
get
(
'ENABLE_COURSEWARE_SEARCH'
):
# Use ElasticSearch as the search engine herein
SEARCH_ENGINE
=
"search.elastic.ElasticSearchEngine"
# Facebook app
FACEBOOK_API_VERSION
=
AUTH_TOKENS
.
get
(
"FACEBOOK_API_VERSION"
)
FACEBOOK_APP_SECRET
=
AUTH_TOKENS
.
get
(
"FACEBOOK_APP_SECRET"
)
FACEBOOK_APP_ID
=
AUTH_TOKENS
.
get
(
"FACEBOOK_APP_ID"
)
lms/envs/common.py
View file @
818d59e8
...
...
@@ -312,6 +312,7 @@ FEATURES = {
# Expose Mobile REST API. Note that if you use this, you must also set
# ENABLE_OAUTH2_PROVIDER to True
'ENABLE_MOBILE_REST_API'
:
False
,
'ENABLE_MOBILE_SOCIAL_FACEBOOK_FEATURES'
:
False
,
# Enable the combined login/registration form
'ENABLE_COMBINED_LOGIN_REGISTRATION'
:
False
,
...
...
lms/envs/test.py
View file @
818d59e8
...
...
@@ -263,6 +263,7 @@ FEATURES['ENABLE_OAUTH2_PROVIDER'] = True
########################### External REST APIs #################################
FEATURES
[
'ENABLE_MOBILE_REST_API'
]
=
True
FEATURES
[
'ENABLE_MOBILE_SOCIAL_FACEBOOK_FEATURES'
]
=
True
FEATURES
[
'ENABLE_VIDEO_ABSTRACTION_LAYER_API'
]
=
True
###################### Payment ##############################3
...
...
@@ -460,3 +461,7 @@ FEATURES['ENTRANCE_EXAMS'] = True
FEATURES
[
'ENABLE_COURSEWARE_SEARCH'
]
=
True
# Use MockSearchEngine as the search engine for test scenario
SEARCH_ENGINE
=
"search.tests.mock_search_engine.MockSearchEngine"
FACEBOOK_APP_SECRET
=
"Test"
FACEBOOK_APP_ID
=
"Test"
FACEBOOK_API_VERSION
=
"v2.2"
requirements/edx/base.txt
View file @
818d59e8
...
...
@@ -36,6 +36,7 @@ django-method-override==0.1.0
djangorestframework==2.3.14
django==1.4.18
elasticsearch==0.4.5
facebook-sdk==0.4.0
feedparser==5.1.3
firebase-token-generator==1.3.2
# Master pyfs has a bug working with VPC auth. This is a fix. We should switch
...
...
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