From fa8ca11d8c4f9fa18ffa3e16471b0233d2a2b35c Mon Sep 17 00:00:00 2001 From: Nimisha Asthagiri Date: Mon, 25 Jan 2016 10:40:35 -0500 Subject: [PATCH] Remove unused Mobile Social Facebook endpoint --- .../models/settings/course_metadata.py | 4 - common/lib/xmodule/xmodule/course_module.py | 9 - .../mobile_api/social_facebook/__init__.py | 42 --- .../social_facebook/courses/__init__.py | 3 - .../social_facebook/courses/models.py | 3 - .../social_facebook/courses/serializers.py | 11 - .../social_facebook/courses/tests.py | 147 -------- .../social_facebook/courses/urls.py | 15 - .../social_facebook/courses/views.py | 65 ---- .../social_facebook/friends/__init__.py | 3 - .../social_facebook/friends/models.py | 3 - .../social_facebook/friends/serializers.py | 11 - .../social_facebook/friends/tests.py | 318 ------------------ .../social_facebook/friends/urls.py | 16 - .../social_facebook/friends/views.py | 71 ---- .../social_facebook/groups/__init__.py | 3 - .../social_facebook/groups/models.py | 3 - .../social_facebook/groups/serializers.py | 30 -- .../social_facebook/groups/tests.py | 199 ----------- .../mobile_api/social_facebook/groups/urls.py | 20 -- .../social_facebook/groups/views.py | 143 -------- .../mobile_api/social_facebook/models.py | 3 - .../social_facebook/preferences/__init__.py | 3 - .../social_facebook/preferences/models.py | 3 - .../preferences/serializers.py | 11 - .../social_facebook/preferences/tests.py | 67 ---- .../social_facebook/preferences/urls.py | 14 - .../social_facebook/preferences/views.py | 52 --- .../mobile_api/social_facebook/test_utils.py | 186 ---------- .../mobile_api/social_facebook/urls.py | 11 - .../mobile_api/social_facebook/utils.py | 76 ----- lms/djangoapps/mobile_api/urls.py | 7 - .../mobile_api/users/serializers.py | 5 +- lms/djangoapps/mobile_api/users/tests.py | 17 - lms/envs/common.py | 1 - lms/envs/test.py | 1 - ...0008_remove_courseoverview_facebook_url.py | 18 + .../content/course_overviews/models.py | 2 - .../content/course_overviews/tests.py | 1 - 39 files changed, 19 insertions(+), 1578 deletions(-) delete mode 100644 lms/djangoapps/mobile_api/social_facebook/__init__.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/courses/__init__.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/courses/models.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/courses/serializers.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/courses/tests.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/courses/urls.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/courses/views.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/friends/__init__.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/friends/models.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/friends/serializers.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/friends/tests.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/friends/urls.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/friends/views.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/groups/__init__.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/groups/models.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/groups/serializers.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/groups/tests.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/groups/urls.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/groups/views.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/models.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/preferences/__init__.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/preferences/models.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/preferences/serializers.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/preferences/tests.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/preferences/urls.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/preferences/views.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/test_utils.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/urls.py delete mode 100644 lms/djangoapps/mobile_api/social_facebook/utils.py create mode 100644 openedx/core/djangoapps/content/course_overviews/migrations/0008_remove_courseoverview_facebook_url.py diff --git a/cms/djangoapps/models/settings/course_metadata.py b/cms/djangoapps/models/settings/course_metadata.py index 176669f9fe..ad253e1506 100644 --- a/cms/djangoapps/models/settings/course_metadata.py +++ b/cms/djangoapps/models/settings/course_metadata.py @@ -73,10 +73,6 @@ class CourseMetadata(object): if not settings.FEATURES.get('ENABLE_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. if (not hasattr(settings, 'SOCIAL_SHARING_SETTINGS') or not getattr(settings, 'SOCIAL_SHARING_SETTINGS', {}).get("CUSTOM_COURSE_URLS")): diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py index 03a835244f..1f09dfce23 100644 --- a/common/lib/xmodule/xmodule/course_module.py +++ b/common/lib/xmodule/xmodule/course_module.py @@ -338,15 +338,6 @@ class CourseFields(object): help=_("Enter the unique identifier for your course's video files provided by edX."), 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( display_name=_("Course Not Graded"), help=_("Enter true or false. If true, the course will not be graded."), diff --git a/lms/djangoapps/mobile_api/social_facebook/__init__.py b/lms/djangoapps/mobile_api/social_facebook/__init__.py deleted file mode 100644 index 3a45c5076a..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/__init__.py +++ /dev/null @@ -1,42 +0,0 @@ -""" -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. diff --git a/lms/djangoapps/mobile_api/social_facebook/courses/__init__.py b/lms/djangoapps/mobile_api/social_facebook/courses/__init__.py deleted file mode 100644 index 738aa67827..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/courses/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Courses API -""" diff --git a/lms/djangoapps/mobile_api/social_facebook/courses/models.py b/lms/djangoapps/mobile_api/social_facebook/courses/models.py deleted file mode 100644 index d2e8572729..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/courses/models.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -A models.py is required to make this an app (until we move to Django 1.7) -""" diff --git a/lms/djangoapps/mobile_api/social_facebook/courses/serializers.py b/lms/djangoapps/mobile_api/social_facebook/courses/serializers.py deleted file mode 100644 index bccd9c3fd9..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/courses/serializers.py +++ /dev/null @@ -1,11 +0,0 @@ -""" -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) diff --git a/lms/djangoapps/mobile_api/social_facebook/courses/tests.py b/lms/djangoapps/mobile_api/social_facebook/courses/tests.py deleted file mode 100644 index 26155b33eb..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/courses/tests.py +++ /dev/null @@ -1,147 +0,0 @@ -# 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) diff --git a/lms/djangoapps/mobile_api/social_facebook/courses/urls.py b/lms/djangoapps/mobile_api/social_facebook/courses/urls.py deleted file mode 100644 index 8e0b93093c..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/courses/urls.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -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' - ), -) diff --git a/lms/djangoapps/mobile_api/social_facebook/courses/views.py b/lms/djangoapps/mobile_api/social_facebook/courses/views.py deleted file mode 100644 index b4f1a23aeb..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/courses/views.py +++ /dev/null @@ -1,65 +0,0 @@ -""" - 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) diff --git a/lms/djangoapps/mobile_api/social_facebook/friends/__init__.py b/lms/djangoapps/mobile_api/social_facebook/friends/__init__.py deleted file mode 100644 index 378fd53a74..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/friends/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Friends API -""" diff --git a/lms/djangoapps/mobile_api/social_facebook/friends/models.py b/lms/djangoapps/mobile_api/social_facebook/friends/models.py deleted file mode 100644 index d2e8572729..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/friends/models.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -A models.py is required to make this an app (until we move to Django 1.7) -""" diff --git a/lms/djangoapps/mobile_api/social_facebook/friends/serializers.py b/lms/djangoapps/mobile_api/social_facebook/friends/serializers.py deleted file mode 100644 index 449bbbced3..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/friends/serializers.py +++ /dev/null @@ -1,11 +0,0 @@ -""" -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) diff --git a/lms/djangoapps/mobile_api/social_facebook/friends/tests.py b/lms/djangoapps/mobile_api/social_facebook/friends/tests.py deleted file mode 100644 index 3db80aae6f..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/friends/tests.py +++ /dev/null @@ -1,318 +0,0 @@ -# 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']) diff --git a/lms/djangoapps/mobile_api/social_facebook/friends/urls.py b/lms/djangoapps/mobile_api/social_facebook/friends/urls.py deleted file mode 100644 index e6e5f9141c..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/friends/urls.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -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' - ), -) diff --git a/lms/djangoapps/mobile_api/social_facebook/friends/views.py b/lms/djangoapps/mobile_api/social_facebook/friends/views.py deleted file mode 100644 index 7e1904491d..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/friends/views.py +++ /dev/null @@ -1,71 +0,0 @@ -""" - 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/ - - 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}) diff --git a/lms/djangoapps/mobile_api/social_facebook/groups/__init__.py b/lms/djangoapps/mobile_api/social_facebook/groups/__init__.py deleted file mode 100644 index ca42614299..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/groups/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Groups API -""" diff --git a/lms/djangoapps/mobile_api/social_facebook/groups/models.py b/lms/djangoapps/mobile_api/social_facebook/groups/models.py deleted file mode 100644 index d2e8572729..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/groups/models.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -A models.py is required to make this an app (until we move to Django 1.7) -""" diff --git a/lms/djangoapps/mobile_api/social_facebook/groups/serializers.py b/lms/djangoapps/mobile_api/social_facebook/groups/serializers.py deleted file mode 100644 index 068d1a04b4..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/groups/serializers.py +++ /dev/null @@ -1,30 +0,0 @@ -""" - 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' - ), - ] - ) diff --git a/lms/djangoapps/mobile_api/social_facebook/groups/tests.py b/lms/djangoapps/mobile_api/social_facebook/groups/tests.py deleted file mode 100644 index 869946b7b2..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/groups/tests.py +++ /dev/null @@ -1,199 +0,0 @@ -""" -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]) diff --git a/lms/djangoapps/mobile_api/social_facebook/groups/urls.py b/lms/djangoapps/mobile_api/social_facebook/groups/urls.py deleted file mode 100644 index a1cbcfc19c..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/groups/urls.py +++ /dev/null @@ -1,20 +0,0 @@ -""" -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[\d]*)$', - Groups.as_view(), - name='create-delete-group' - ), - url( - r'^(?P[\d]+)/member/(?P[\d]*,*)$', - GroupsMembers.as_view(), - name='add-remove-member' - ) -) diff --git a/lms/djangoapps/mobile_api/social_facebook/groups/views.py b/lms/djangoapps/mobile_api/social_facebook/groups/views.py deleted file mode 100644 index 28f0e12cc9..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/groups/views.py +++ /dev/null @@ -1,143 +0,0 @@ -""" -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/ - - **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//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//member/ - - **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)) diff --git a/lms/djangoapps/mobile_api/social_facebook/models.py b/lms/djangoapps/mobile_api/social_facebook/models.py deleted file mode 100644 index d2e8572729..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/models.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -A models.py is required to make this an app (until we move to Django 1.7) -""" diff --git a/lms/djangoapps/mobile_api/social_facebook/preferences/__init__.py b/lms/djangoapps/mobile_api/social_facebook/preferences/__init__.py deleted file mode 100644 index 7a7241c5ad..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/preferences/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Users Sharing preferences API -""" diff --git a/lms/djangoapps/mobile_api/social_facebook/preferences/models.py b/lms/djangoapps/mobile_api/social_facebook/preferences/models.py deleted file mode 100644 index d2e8572729..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/preferences/models.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -A models.py is required to make this an app (until we move to Django 1.7) -""" diff --git a/lms/djangoapps/mobile_api/social_facebook/preferences/serializers.py b/lms/djangoapps/mobile_api/social_facebook/preferences/serializers.py deleted file mode 100644 index 1d51f87eda..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/preferences/serializers.py +++ /dev/null @@ -1,11 +0,0 @@ -""" -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) diff --git a/lms/djangoapps/mobile_api/social_facebook/preferences/tests.py b/lms/djangoapps/mobile_api/social_facebook/preferences/tests.py deleted file mode 100644 index 19850b40f7..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/preferences/tests.py +++ /dev/null @@ -1,67 +0,0 @@ -""" -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') diff --git a/lms/djangoapps/mobile_api/social_facebook/preferences/urls.py b/lms/djangoapps/mobile_api/social_facebook/preferences/urls.py deleted file mode 100644 index 70f95bc10c..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/preferences/urls.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -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' - ), -) diff --git a/lms/djangoapps/mobile_api/social_facebook/preferences/views.py b/lms/djangoapps/mobile_api/social_facebook/preferences/views.py deleted file mode 100644 index 8170483e6d..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/preferences/views.py +++ /dev/null @@ -1,52 +0,0 @@ -""" -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) diff --git a/lms/djangoapps/mobile_api/social_facebook/test_utils.py b/lms/djangoapps/mobile_api/social_facebook/test_utils.py deleted file mode 100644 index c9c7304e10..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/test_utils.py +++ /dev/null @@ -1,186 +0,0 @@ -""" -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) diff --git a/lms/djangoapps/mobile_api/social_facebook/urls.py b/lms/djangoapps/mobile_api/social_facebook/urls.py deleted file mode 100644 index 6118f592a6..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/urls.py +++ /dev/null @@ -1,11 +0,0 @@ -""" -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')), -) diff --git a/lms/djangoapps/mobile_api/social_facebook/utils.py b/lms/djangoapps/mobile_api/social_facebook/utils.py deleted file mode 100644 index 654dd97edd..0000000000 --- a/lms/djangoapps/mobile_api/social_facebook/utils.py +++ /dev/null @@ -1,76 +0,0 @@ -""" -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' diff --git a/lms/djangoapps/mobile_api/urls.py b/lms/djangoapps/mobile_api/urls.py index 103aa3babb..864fb9fdf3 100644 --- a/lms/djangoapps/mobile_api/urls.py +++ b/lms/djangoapps/mobile_api/urls.py @@ -1,7 +1,6 @@ """ URLs for mobile API """ -from django.conf import settings from django.conf.urls import patterns, url, include from .users.views import my_user_info @@ -13,9 +12,3 @@ 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')), - ) diff --git a/lms/djangoapps/mobile_api/users/serializers.py b/lms/djangoapps/mobile_api/users/serializers.py index b2be7c81c8..57f71d8d0a 100644 --- a/lms/djangoapps/mobile_api/users/serializers.py +++ b/lms/djangoapps/mobile_api/users/serializers.py @@ -77,10 +77,7 @@ class CourseOverviewField(serializers.RelatedField): request=request, ), - # Note: The following 2 should be deprecated. - 'social_urls': { - 'facebook': course_overview.facebook_url, - }, + # Note: The following should be deprecated. 'latest_updates': { 'video': None }, diff --git a/lms/djangoapps/mobile_api/users/tests.py b/lms/djangoapps/mobile_api/users/tests.py index 1a831b777c..270587e8b7 100644 --- a/lms/djangoapps/mobile_api/users/tests.py +++ b/lms/djangoapps/mobile_api/users/tests.py @@ -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}) def test_discussion_url(self): self.login_and_enroll() diff --git a/lms/envs/common.py b/lms/envs/common.py index 0a6f3d6ab5..92742cf7aa 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -271,7 +271,6 @@ 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 temporary APIs required for xBlocks on Mobile 'ENABLE_COURSE_BLOCKS_NAVIGATION_API': False, diff --git a/lms/envs/test.py b/lms/envs/test.py index 54411c6693..dcf28d61d6 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -293,7 +293,6 @@ OIDC_COURSE_HANDLER_CACHE_TIMEOUT = 0 ########################### External REST APIs ################################# FEATURES['ENABLE_MOBILE_REST_API'] = True -FEATURES['ENABLE_MOBILE_SOCIAL_FACEBOOK_FEATURES'] = True FEATURES['ENABLE_VIDEO_ABSTRACTION_LAYER_API'] = True FEATURES['ENABLE_COURSE_BLOCKS_NAVIGATION_API'] = True diff --git a/openedx/core/djangoapps/content/course_overviews/migrations/0008_remove_courseoverview_facebook_url.py b/openedx/core/djangoapps/content/course_overviews/migrations/0008_remove_courseoverview_facebook_url.py new file mode 100644 index 0000000000..91ae94cad0 --- /dev/null +++ b/openedx/core/djangoapps/content/course_overviews/migrations/0008_remove_courseoverview_facebook_url.py @@ -0,0 +1,18 @@ +# -*- 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', + ), + ] diff --git a/openedx/core/djangoapps/content/course_overviews/models.py b/openedx/core/djangoapps/content/course_overviews/models.py index 5956ec55d3..0160dc15d5 100644 --- a/openedx/core/djangoapps/content/course_overviews/models.py +++ b/openedx/core/djangoapps/content/course_overviews/models.py @@ -66,7 +66,6 @@ class CourseOverview(TimeStampedModel): # URLs course_image_url = TextField() - facebook_url = TextField(null=True) social_sharing_url = TextField(null=True) end_of_course_survey_url = TextField(null=True) @@ -156,7 +155,6 @@ class CourseOverview(TimeStampedModel): announcement=course.announcement, course_image_url=course_image_url(course), - facebook_url=course.facebook_url, social_sharing_url=course.social_sharing_url, certificates_display_behavior=course.certificates_display_behavior, diff --git a/openedx/core/djangoapps/content/course_overviews/tests.py b/openedx/core/djangoapps/content/course_overviews/tests.py index fe0e66bbf4..988cfda7af 100644 --- a/openedx/core/djangoapps/content/course_overviews/tests.py +++ b/openedx/core/djangoapps/content/course_overviews/tests.py @@ -91,7 +91,6 @@ class CourseOverviewTestCase(ModuleStoreTestCase): 'display_number_with_default', 'display_org_with_default', 'advertised_start', - 'facebook_url', 'social_sharing_url', 'certificates_display_behavior', 'certificates_show_before_end',