{"action":"created","comment":{"url":"https://api.github.com/repos/edx/edx-platform/pulls/comments/30886034","id":30886034,"diff_hunk":"@@ -0,0 +1,278 @@\n+\"\"\"\n+For more information, see:\n+https://openedx.atlassian.net/wiki/display/TNL/Bookmarks+API\n+\"\"\"\n+import logging\n+\n+from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned\n+from django.utils.translation import ugettext as _\n+\n+from rest_framework import status\n+from rest_framework import permissions\n+from rest_framework.authentication import OAuth2Authentication, SessionAuthentication\n+from rest_framework.generics import ListCreateAPIView\n+from rest_framework.response import Response\n+from rest_framework.views import APIView\n+\n+from opaque_keys import InvalidKeyError\n+from opaque_keys.edx.keys import CourseKey, UsageKey\n+\n+from bookmarks.serializers import BookmarkSerializer\n+from openedx.core.lib.api.serializers import PaginationSerializer\n+\n+from xmodule.modulestore.exceptions import ItemNotFoundError\n+from xmodule.modulestore.django import modulestore\n+\n+from .models import Bookmark\n+from .api import get_bookmark\n+\n+\n+log = logging.getLogger(__name__)\n+\n+DEFAULT_FIELDS = [\"id\", \"course_id\", \"usage_id\", \"created\"]\n+OPTIONAL_FIELDS = ['display_name', 'path']\n+\n+\n+class BookmarksView(ListCreateAPIView):\n+    \"\"\"\n+    **Use Case**\n+\n+        Get a paginated list of bookmarks in particular course.\n+        Each page in the list can contain up to 30 bookmarks by default.\n+\n+        Create/Post a new bookmark for particular Xblock.\n+\n+    **Example Requests**\n+\n+          GET /api/bookmarks/v0/bookmarks/?course_id={course_id1}\n+\n+          POST /api/bookmarks/v0/bookmarks/?course_id={course_id1}\n+\n+    **Response Values**\n+\n+        * count: The number of bookmarks in a course.\n+\n+        * next: The URI to the next page of bookmarks.\n+\n+        * previous: The URI to the previous page of bookmarks.\n+\n+        * num_pages: The number of pages listing bookmarks.\n+\n+        * results:  A list of bookmarks returned. Each collection in the list\n+          contains these fields.\n+\n+            * id: String. The identifier string for the bookmark: {user_id},{usage_id}.\n+\n+            * course_id: String. The identifier string of the bookmark's course.\n+\n+            * usage_id: String. The identifier string of the bookmark's XBlock.\n+\n+            * display_name: (optional) String. Display name of the XBlock.\n+\n+            * path: (optional) List. List of dicts containing {\"usage_id\": \"\", display_name:\"\"} for the XBlocks\n+                from the top of the course tree till the parent of the bookmarked XBlock.\n+\n+            * created: ISO 8601 String. The timestamp of bookmark's creation.\n+\n+    \"\"\"\n+    authentication_classes = (OAuth2Authentication, SessionAuthentication)\n+    permission_classes = (permissions.IsAuthenticated,)\n+\n+    paginate_by = 30\n+    paginate_by_param = 'page_size'\n+    pagination_serializer_class = PaginationSerializer\n+    serializer_class = BookmarkSerializer\n+\n+    def get_serializer_context(self):\n+        \"\"\"\n+        Extra context provided to the serializer class.\n+        \"\"\"\n+        context = super(BookmarksView, self).get_serializer_context()\n+        if self.request.method == 'POST':\n+            return context\n+        optional_fields = self.request.QUERY_PARAMS.get('fields', [])\n+        optional_fields_list = optional_fields.split(',') if optional_fields else []\n+        context['fields'] = DEFAULT_FIELDS + [field for field in optional_fields_list if field in OPTIONAL_FIELDS]\n+        return context\n+\n+    def get_queryset(self):\n+        course_id = self.request.QUERY_PARAMS.get('course_id', None)\n+\n+        try:\n+            course_key = CourseKey.from_string(course_id)\n+        except InvalidKeyError:\n+            log.error(\"Invalid course id '{course_id}'\")\n+            return []\n+\n+        return Bookmark.objects.filter(course_key=course_key, user=self.request.user).order_by('-created')\n+\n+    def post(self, request):\n+        \"\"\"\n+        POST /api/bookmarks/v0/bookmarks/?course_id={course_id1}\n+        \"\"\"\n+        if not request.DATA:\n+            error_message = _(\"No data provided\")\n+            return Response(\n+                {\n+                    \"developer_message\": error_message,\n+                    \"user_message\": error_message\n+                },\n+                status=status.HTTP_400_BAD_REQUEST\n+            )\n+\n+        usage_id = request.DATA.get('usage_id', None)\n+        if not usage_id:\n+            error_message = _('No usage id provided')\n+            return Response(\n+                {\n+                    \"developer_message\": error_message,\n+                    \"user_message\": error_message\n+                },\n+                status=status.HTTP_400_BAD_REQUEST\n+            )\n+\n+        try:\n+            usage_key = UsageKey.from_string(usage_id)\n+\n+            # usage_key's course_key may have an empty run property\n+            usage_key = usage_key.replace(course_key=modulestore().fill_in_run(usage_key.course_key))\n+            course_key = usage_key.course_key\n+        except InvalidKeyError as exception:\n+            log.error(exception.message)\n+            message = _(u\"Invalid usage id\")\n+            return Response(\n+                {\n+                    \"developer_message\": message,\n+                    \"user_message\": message\n+                },\n+                status=status.HTTP_400_BAD_REQUEST\n+            )\n+\n+        bookmarks_data = {\n+            \"usage_key\": usage_key,\n+            \"course_key\": course_key,\n+            \"user\": request.user\n+        }\n+\n+        try:\n+            bookmark = Bookmark.create(bookmarks_data)\n+        except ItemNotFoundError as exception:\n+            log.error(exception.message)\n+            return Response(\n+                {\n+                    \"developer_message\": u\"Item with usage id not found\",","path":"lms/djangoapps/bookmarks/views.py","position":163,"original_position":163,"commit_id":"c75268b4b1485f75f1a063f90f076bc83f6db4e6","original_commit_id":"c75268b4b1485f75f1a063f90f076bc83f6db4e6","user":{"login":"symbolist","id":5305552,"avatar_url":"https://avatars.githubusercontent.com/u/5305552?v=3","gravatar_id":"","url":"https://api.github.com/users/symbolist","html_url":"https://github.com/symbolist","followers_url":"https://api.github.com/users/symbolist/followers","following_url":"https://api.github.com/users/symbolist/following{/other_user}","gists_url":"https://api.github.com/users/symbolist/gists{/gist_id}","starred_url":"https://api.github.com/users/symbolist/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/symbolist/subscriptions","organizations_url":"https://api.github.com/users/symbolist/orgs","repos_url":"https://api.github.com/users/symbolist/repos","events_url":"https://api.github.com/users/symbolist/events{/privacy}","received_events_url":"https://api.github.com/users/symbolist/received_events","type":"User","site_admin":false},"body":"\"Block with usage_id not found.\"","created_at":"2015-05-22T10:55:26Z","updated_at":"2015-05-22T10:55:26Z","html_url":"https://github.com/edx/edx-platform/pull/8082#discussion_r30886034","pull_request_url":"https://api.github.com/repos/edx/edx-platform/pulls/8082","_links":{"self":{"href":"https://api.github.com/repos/edx/edx-platform/pulls/comments/30886034"},"html":{"href":"https://github.com/edx/edx-platform/pull/8082#discussion_r30886034"},"pull_request":{"href":"https://api.github.com/repos/edx/edx-platform/pulls/8082"}}},"pull_request":{"url":"https://api.github.com/repos/edx/edx-platform/pulls/8082","id":35614489,"html_url":"https://github.com/edx/edx-platform/pull/8082","diff_url":"https://github.com/edx/edx-platform/pull/8082.diff","patch_url":"https://github.com/edx/edx-platform/pull/8082.patch","issue_url":"https://api.github.com/repos/edx/edx-platform/issues/8082","number":8082,"state":"open","locked":false,"title":"Bookmarks API implementation","user":{"login":"muzaffaryousaf","id":2994509,"avatar_url":"https://avatars.githubusercontent.com/u/2994509?v=3","gravatar_id":"","url":"https://api.github.com/users/muzaffaryousaf","html_url":"https://github.com/muzaffaryousaf","followers_url":"https://api.github.com/users/muzaffaryousaf/followers","following_url":"https://api.github.com/users/muzaffaryousaf/following{/other_user}","gists_url":"https://api.github.com/users/muzaffaryousaf/gists{/gist_id}","starred_url":"https://api.github.com/users/muzaffaryousaf/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/muzaffaryousaf/subscriptions","organizations_url":"https://api.github.com/users/muzaffaryousaf/orgs","repos_url":"https://api.github.com/users/muzaffaryousaf/repos","events_url":"https://api.github.com/users/muzaffaryousaf/events{/privacy}","received_events_url":"https://api.github.com/users/muzaffaryousaf/received_events","type":"User","site_admin":false},"body":"Jira ticket: https://openedx.atlassian.net/browse/TNL-1945","created_at":"2015-05-18T09:08:10Z","updated_at":"2015-05-22T10:55:26Z","closed_at":null,"merged_at":null,"merge_commit_sha":"bf6c18e6bacf5d8c6ad5b2466622a5a58575a232","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/edx/edx-platform/pulls/8082/commits","review_comments_url":"https://api.github.com/repos/edx/edx-platform/pulls/8082/comments","review_comment_url":"https://api.github.com/repos/edx/edx-platform/pulls/comments{/number}","comments_url":"https://api.github.com/repos/edx/edx-platform/issues/8082/comments","statuses_url":"https://api.github.com/repos/edx/edx-platform/statuses/c75268b4b1485f75f1a063f90f076bc83f6db4e6","head":{"label":"edx:muzaffar/tnl1945-bookmarks-api","ref":"muzaffar/tnl1945-bookmarks-api","sha":"c75268b4b1485f75f1a063f90f076bc83f6db4e6","user":{"login":"edx","id":3179841,"avatar_url":"https://avatars.githubusercontent.com/u/3179841?v=3","gravatar_id":"","url":"https://api.github.com/users/edx","html_url":"https://github.com/edx","followers_url":"https://api.github.com/users/edx/followers","following_url":"https://api.github.com/users/edx/following{/other_user}","gists_url":"https://api.github.com/users/edx/gists{/gist_id}","starred_url":"https://api.github.com/users/edx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx/subscriptions","organizations_url":"https://api.github.com/users/edx/orgs","repos_url":"https://api.github.com/users/edx/repos","events_url":"https://api.github.com/users/edx/events{/privacy}","received_events_url":"https://api.github.com/users/edx/received_events","type":"Organization","site_admin":false},"repo":{"id":10391073,"name":"edx-platform","full_name":"edx/edx-platform","owner":{"login":"edx","id":3179841,"avatar_url":"https://avatars.githubusercontent.com/u/3179841?v=3","gravatar_id":"","url":"https://api.github.com/users/edx","html_url":"https://github.com/edx","followers_url":"https://api.github.com/users/edx/followers","following_url":"https://api.github.com/users/edx/following{/other_user}","gists_url":"https://api.github.com/users/edx/gists{/gist_id}","starred_url":"https://api.github.com/users/edx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx/subscriptions","organizations_url":"https://api.github.com/users/edx/orgs","repos_url":"https://api.github.com/users/edx/repos","events_url":"https://api.github.com/users/edx/events{/privacy}","received_events_url":"https://api.github.com/users/edx/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/edx/edx-platform","description":"Open edX, the edX platform that powers http://edx.org","fork":false,"url":"https://api.github.com/repos/edx/edx-platform","forks_url":"https://api.github.com/repos/edx/edx-platform/forks","keys_url":"https://api.github.com/repos/edx/edx-platform/keys{/key_id}","collaborators_url":"https://api.github.com/repos/edx/edx-platform/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/edx/edx-platform/teams","hooks_url":"https://api.github.com/repos/edx/edx-platform/hooks","issue_events_url":"https://api.github.com/repos/edx/edx-platform/issues/events{/number}","events_url":"https://api.github.com/repos/edx/edx-platform/events","assignees_url":"https://api.github.com/repos/edx/edx-platform/assignees{/user}","branches_url":"https://api.github.com/repos/edx/edx-platform/branches{/branch}","tags_url":"https://api.github.com/repos/edx/edx-platform/tags","blobs_url":"https://api.github.com/repos/edx/edx-platform/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/edx/edx-platform/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/edx/edx-platform/git/refs{/sha}","trees_url":"https://api.github.com/repos/edx/edx-platform/git/trees{/sha}","statuses_url":"https://api.github.com/repos/edx/edx-platform/statuses/{sha}","languages_url":"https://api.github.com/repos/edx/edx-platform/languages","stargazers_url":"https://api.github.com/repos/edx/edx-platform/stargazers","contributors_url":"https://api.github.com/repos/edx/edx-platform/contributors","subscribers_url":"https://api.github.com/repos/edx/edx-platform/subscribers","subscription_url":"https://api.github.com/repos/edx/edx-platform/subscription","commits_url":"https://api.github.com/repos/edx/edx-platform/commits{/sha}","git_commits_url":"https://api.github.com/repos/edx/edx-platform/git/commits{/sha}","comments_url":"https://api.github.com/repos/edx/edx-platform/comments{/number}","issue_comment_url":"https://api.github.com/repos/edx/edx-platform/issues/comments{/number}","contents_url":"https://api.github.com/repos/edx/edx-platform/contents/{+path}","compare_url":"https://api.github.com/repos/edx/edx-platform/compare/{base}...{head}","merges_url":"https://api.github.com/repos/edx/edx-platform/merges","archive_url":"https://api.github.com/repos/edx/edx-platform/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/edx/edx-platform/downloads","issues_url":"https://api.github.com/repos/edx/edx-platform/issues{/number}","pulls_url":"https://api.github.com/repos/edx/edx-platform/pulls{/number}","milestones_url":"https://api.github.com/repos/edx/edx-platform/milestones{/number}","notifications_url":"https://api.github.com/repos/edx/edx-platform/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/edx/edx-platform/labels{/name}","releases_url":"https://api.github.com/repos/edx/edx-platform/releases{/id}","created_at":"2013-05-30T20:20:38Z","updated_at":"2015-05-22T06:27:15Z","pushed_at":"2015-05-22T10:50:29Z","git_url":"git://github.com/edx/edx-platform.git","ssh_url":"git@github.com:edx/edx-platform.git","clone_url":"https://github.com/edx/edx-platform.git","svn_url":"https://github.com/edx/edx-platform","homepage":"http://openedx.org/","size":1945299,"stargazers_count":1935,"watchers_count":1935,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":1114,"mirror_url":null,"open_issues_count":169,"forks":1114,"open_issues":169,"watchers":1935,"default_branch":"master"}},"base":{"label":"edx:unit-bookmarking","ref":"unit-bookmarking","sha":"930ccd633256353fb421e522ea24f32144e8abf1","user":{"login":"edx","id":3179841,"avatar_url":"https://avatars.githubusercontent.com/u/3179841?v=3","gravatar_id":"","url":"https://api.github.com/users/edx","html_url":"https://github.com/edx","followers_url":"https://api.github.com/users/edx/followers","following_url":"https://api.github.com/users/edx/following{/other_user}","gists_url":"https://api.github.com/users/edx/gists{/gist_id}","starred_url":"https://api.github.com/users/edx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx/subscriptions","organizations_url":"https://api.github.com/users/edx/orgs","repos_url":"https://api.github.com/users/edx/repos","events_url":"https://api.github.com/users/edx/events{/privacy}","received_events_url":"https://api.github.com/users/edx/received_events","type":"Organization","site_admin":false},"repo":{"id":10391073,"name":"edx-platform","full_name":"edx/edx-platform","owner":{"login":"edx","id":3179841,"avatar_url":"https://avatars.githubusercontent.com/u/3179841?v=3","gravatar_id":"","url":"https://api.github.com/users/edx","html_url":"https://github.com/edx","followers_url":"https://api.github.com/users/edx/followers","following_url":"https://api.github.com/users/edx/following{/other_user}","gists_url":"https://api.github.com/users/edx/gists{/gist_id}","starred_url":"https://api.github.com/users/edx/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/edx/subscriptions","organizations_url":"https://api.github.com/users/edx/orgs","repos_url":"https://api.github.com/users/edx/repos","events_url":"https://api.github.com/users/edx/events{/privacy}","received_events_url":"https://api.github.com/users/edx/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/edx/edx-platform","description":"Open edX, the edX platform that powers http://edx.org","fork":false,"url":"https://api.github.com/repos/edx/edx-platform","forks_url":"https://api.github.com/repos/edx/edx-platform/forks","keys_url":"https://api.github.com/repos/edx/edx-platform/keys{/key_id}","collaborators_url":"https://api.github.com/repos/edx/edx-platform/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/edx/edx-platform/teams","hooks_url":"https://api.github.com/repos/edx/edx-platform/hooks","issue_events_url":"https://api.github.com/repos/edx/edx-platform/issues/events{/number}","events_url":"https://api.github.com/repos/edx/edx-platform/events","assignees_url":"https://api.github.com/repos/edx/edx-platform/assignees{/user}","branches_url":"https://api.github.com/repos/edx/edx-platform/branches{/branch}","tags_url":"https://api.github.com/repos/edx/edx-platform/tags","blobs_url":"https://api.github.com/repos/edx/edx-platform/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/edx/edx-platform/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/edx/edx-platform/git/refs{/sha}","trees_url":"https://api.github.com/repos/edx/edx-platform/git/trees{/sha}","statuses_url":"https://api.github.com/repos/edx/edx-platform/statuses/{sha}","languages_url":"https://api.github.com/repos/edx/edx-platform/languages","stargazers_url":"https://api.github.com/repos/edx/edx-platform/stargazers","contributors_url":"https://api.github.com/repos/edx/edx-platform/contributors","subscribers_url":"https://api.github.com/repos/edx/edx-platform/subscribers","subscription_url":"https://api.github.com/repos/edx/edx-platform/subscription","commits_url":"https://api.github.com/repos/edx/edx-platform/commits{/sha}","git_commits_url":"https://api.github.com/repos/edx/edx-platform/git/commits{/sha}","comments_url":"https://api.github.com/repos/edx/edx-platform/comments{/number}","issue_comment_url":"https://api.github.com/repos/edx/edx-platform/issues/comments{/number}","contents_url":"https://api.github.com/repos/edx/edx-platform/contents/{+path}","compare_url":"https://api.github.com/repos/edx/edx-platform/compare/{base}...{head}","merges_url":"https://api.github.com/repos/edx/edx-platform/merges","archive_url":"https://api.github.com/repos/edx/edx-platform/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/edx/edx-platform/downloads","issues_url":"https://api.github.com/repos/edx/edx-platform/issues{/number}","pulls_url":"https://api.github.com/repos/edx/edx-platform/pulls{/number}","milestones_url":"https://api.github.com/repos/edx/edx-platform/milestones{/number}","notifications_url":"https://api.github.com/repos/edx/edx-platform/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/edx/edx-platform/labels{/name}","releases_url":"https://api.github.com/repos/edx/edx-platform/releases{/id}","created_at":"2013-05-30T20:20:38Z","updated_at":"2015-05-22T06:27:15Z","pushed_at":"2015-05-22T10:50:29Z","git_url":"git://github.com/edx/edx-platform.git","ssh_url":"git@github.com:edx/edx-platform.git","clone_url":"https://github.com/edx/edx-platform.git","svn_url":"https://github.com/edx/edx-platform","homepage":"http://openedx.org/","size":1945299,"stargazers_count":1935,"watchers_count":1935,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":1114,"mirror_url":null,"open_issues_count":169,"forks":1114,"open_issues":169,"watchers":1935,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/edx/edx-platform/pulls/8082"},"html":{"href":"https://github.com/edx/edx-platform/pull/8082"},"issue":{"href":"https://api.github.com/repos/edx/edx-platform/issues/8082"},"comments":{"href":"https://api.github.com/repos/edx/edx-platform/issues/8082/comments"},"review_comments":{"href":"https://api.github.com/repos/edx/edx-platform/pulls/8082/comments"},"review_comment":{"href":"https://api.github.com/repos/edx/edx-platform/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/edx/edx-platform/pulls/8082/commits"},"statuses":{"href":"https://api.github.com/repos/edx/edx-platform/statuses/c75268b4b1485f75f1a063f90f076bc83f6db4e6"}}}}