{"action":"created","comment":{"url":"https://api.github.com/repos/edx/edx-platform/pulls/comments/32065138","id":32065138,"diff_hunk":"@@ -290,3 +297,362 @@ class CourseGradingPolicy(CourseViewMixin, ListAPIView):\n     @CourseViewMixin.course_check\n     def get(self, request, **kwargs):\n         return Response(api.course_grading_policy(self.course_key))\n+\n+\n+@view_auth_classes()\n+class CourseBlocksAndNavigation(ListAPIView):\n+    \"\"\"\n+    **Use Case**\n+\n+        The following endpoints return the content of the course according to the requesting user's access level.\n+\n+        * Blocks - Get the course's blocks.\n+\n+        * Navigation - Get the course's navigation information per the navigation depth requested.\n+\n+        * Blocks+Navigation - Get both the course's blocks and the course's navigation information.\n+\n+    **Example requests**:\n+\n+        GET api/course_structure/v0/courses/{course_id}/blocks/\n+        GET api/course_structure/v0/courses/{course_id}/navigation/\n+        GET api/course_structure/v0/courses/{course_id}/blocks+navigation/\n+           &block_count=video\n+           &block_json={\"video\":{\"profiles\":[\"mobile_low\"]}}\n+           &fields=graded,format,responsive_ui\n+\n+    **Parameters**:\n+\n+        * block_json: (dict) Indicates for which block types to return student_view_json data.  The key is the block\n+          type and the value is the \"context\" that is passed to the block's student_view_json method.\n+\n+          Example: block_json={\"video\":{\"profiles\":[\"mobile_high\",\"mobile_low\"]}}\n+\n+        * block_count: (list) Indicates for which block types to return the aggregate count of the blocks.\n+\n+          Example: block_count=\"video,problem\"\n+\n+        * fields: (list) Indicates which additional fields to return for each block.\n+          Default is \"children,graded,format,responsive_ui\"\n+\n+          Example: fields=graded,format,responsive_ui\n+\n+        * navigation_depth (integer) Indicates how far deep to traverse into the course hierarchy before bundling\n+          all the descendants.\n+          Default is 3.\n+\n+          Example: navigation_depth=3\n+\n+    **Response Values**\n+\n+        * root: The ID of the root node of the course blocks.\n+\n+        * blocks: A dictionary that maps block usage IDs to a collection of information about each block.\n+          Each block contains the following fields.\n+\n+          * id: (string) The usage ID of the block.\n+\n+          * type: (string) The type of block. Possible values include course, chapter, sequential, vertical, html,\n+            problem, video, and discussion. The type can also be the name of a custom type of block used for the course.\n+\n+          * display_name: (string) The display name of the block.\n+\n+          * children: (list) If the block has child blocks, a list of IDs of the child blocks.\n+            Returned only if the \"children\" input parameter is True.\n+\n+          * block_count: (dict) For each block type specified in the block_count parameter to the endpoint, the\n+            aggregate number of blocks of that type for this block and all of its descendants.\n+            Returned only if the \"block_count\" input parameter contains this block's type.\n+\n+          * block_json: (dict) The JSON data for this block.\n+            Returned only if the \"block_json\" input parameter contains this block's type.\n+\n+          * block_url: (string) The URL to retrieve the HTML rendering of this block.  The HTML could include\n+            CSS and Javascript code.  This URL can be used as a fallback if the custom block_json for this\n+            block type is not requested and not supported.\n+\n+          * web_url: (string) The URL to the website location of this block.  This URL can be used as a further\n+            fallback if the block_url and the block_json is not supported.\n+\n+          * graded (boolean) Whether or not the block or any of its descendants is graded.\n+            Returned only if \"graded\" is included in the \"fields\" parameter.\n+\n+          * format: (string) The assignment type of the block.\n+            Possible values can be \"Homework\", \"Lab\", \"Midterm Exam\", and \"Final Exam\".\n+            Returned only if \"format\" is included in the \"fields\" parameter.\n+\n+          * responsive_ui: (boolean) Whether or not the block's rendering obtained via block_url is responsive.\n+            Returned only if \"responsive_ui\" is included in the \"fields\" parameter.\n+\n+        * navigation: A dictionary that maps block IDs to a collection of navigation information about each block.\n+          Each block contains the following fields.\n+\n+          * descendants: (list) A list of IDs of the children of the block if the block's depth in the\n+            course hierarchy is less than the navigation_depth.  Otherwise, a list of IDs of the aggregate descendants\n+            of the block.\n+\n+        * blocks_navigation: A dictionary that combines both the blocks and navigation data.\n+\n+    \"\"\"\n+    class RequestInfo(object):\n+        \"\"\"\n+        A class for encapsulating the request information, including what optional fields are requested.\n+        \"\"\"\n+        DEFAULT_FIELDS = \"children,graded,format,responsive_ui\"\n+\n+        def __init__(self, request, course):\n+            self.request = request\n+            self.course = course\n+            self.field_data_cache = None\n+\n+            # check what fields are requested\n+            try:\n+                # fields\n+                self.fields = set(request.GET.get('fields', self.DEFAULT_FIELDS).split(\",\"))\n+\n+                # children\n+                self.children = 'children' in self.fields\n+                self.fields.discard('children')\n+\n+                # block_count\n+                self.block_count = request.GET.get('block_count', \"\")\n+                self.block_count = (\n+                    self.block_count.split(\",\") if self.block_count else []\n+                )\n+\n+                # navigation_depth\n+                self.navigation_depth = int(request.GET.get('navigation_depth', '3'))\n+\n+                # block_json\n+                self.block_json = json.loads(request.GET.get('block_json', \"{}\"))\n+                if self.block_json and not isinstance(self.block_json, dict):\n+                    raise ParseError\n+            except:\n+                raise ParseError\n+\n+    class ResultInfo(object):\n+        \"\"\"\n+        A class for encapsulating the result information, specifically the blocks and navigation data.\n+        \"\"\"\n+        def __init__(self, return_blocks, return_nav):\n+            self.blocks = {}\n+            self.navigation = {}\n+            if return_blocks and return_nav:\n+                self.navigation = self.blocks\n+\n+        def update_response(self, response, return_blocks, return_nav):\n+            \"\"\"\n+            Updates the response object with result information.\n+            \"\"\"\n+            if return_blocks and return_nav:\n+                response[\"blocks+navigation\"] = self.blocks\n+            elif return_blocks:\n+                response[\"blocks\"] = self.blocks\n+            elif return_nav:\n+                response[\"navigation\"] = self.navigation\n+\n+    class BlockInfo(object):\n+        \"\"\"\n+        A class for encapsulating a block's information as needed during traversal of a block hierarcy.\n+        \"\"\"\n+        def __init__(self, block, parent_block_info=None):\n+            # the block for which the recursion is being computed\n+            self.block = block\n+\n+            # the type of the block\n+            self.type = block.category\n+\n+            # the block's depth in the block hierarchy\n+            self.depth = 0\n+\n+            # the block's data to include in the response\n+            self.value = None\n+\n+            # descendants_of_parent: the list of descendants for this block's parent\n+            self.descendants_of_parent = []\n+            self.descendants_of_self = []\n+\n+            # if a parent block was provided, update this block's data based on the parent's data\n+            if parent_block_info:\n+                self.depth = parent_block_info.depth + 1\n+                self.descendants_of_parent = parent_block_info.descendants_of_self\n+\n+    @view_course_access(depth=None)\n+    def list(self, request, course, return_blocks=True, return_nav=True, *args, **kwargs):\n+        \"\"\"\n+        REST API endpoint for listing all the blocks and/or navigation information in the course,\n+        while regarding user access and roles.\n+        \"\"\"\n+        # set starting point\n+        start_block = course\n+\n+        # initialize request and result objects\n+        request_info = self.RequestInfo(request, course)\n+        result_info = self.ResultInfo(return_blocks, return_nav)\n+\n+        # create and populate a field data cache by pre-fetching for the course (with depth=None)\n+        request_info.field_data_cache = FieldDataCache.cache_for_descriptor_descendents(\n+            course.id, request.user, course, depth=None,\n+        )\n+\n+        # start the recursion with the start_block\n+        self.recurse_blocks_nav(request_info, result_info, self.BlockInfo(start_block))\n+\n+        # return response\n+        response = {\"root\": unicode(start_block.location)}\n+        result_info.update_response(response, return_blocks, return_nav)\n+        return Response(response)\n+\n+    def recurse_blocks_nav(self, request_info, result_info, block_info):\n+        \"\"\"\n+        A depth-first recursive function that supports calculation of both the list of blocks in the course\n+        and the navigation information up to requested navigation_depth of the course.\n+\n+        Arguments:\n+        \"\"\"\n+        # bind user data to the block\n+        block_info.block = get_module_for_descriptor(\n+            request_info.request.user,\n+            request_info.request,\n+            block_info.block,\n+            request_info.field_data_cache,\n+            request_info.course.id\n+        )\n+\n+        # verify the user has access to this block\n+        if not has_access(request_info.request.user, 'load', block_info.block, course_key=request_info.course.id):\n+            return\n+\n+        # set basic information about the block\n+        self.set_block_value(request_info, result_info, block_info)\n+\n+        # descendants\n+        self.update_descendants(request_info, result_info, block_info)\n+\n+        # children: recursively call the function for each of the children, while supporting dynamic children.\n+        children = []\n+        if block_info.block.has_children:\n+            children = get_dynamic_descriptor_children(block_info.block, request_info.request.user.id)\n+            for child in children:\n+                self.recurse_blocks_nav(request_info, result_info, self.BlockInfo(child, block_info))\n+            if request_info.children:\n+                block_info.value[\"children\"] = [unicode(child.location) for child in children]\n+\n+        # block count\n+        self.update_block_count(children, request_info, result_info, block_info)\n+\n+        # block JSON data\n+        self.add_block_json(request_info, block_info)\n+\n+        # additional fields\n+        self.add_additional_fields(request_info, block_info)\n+\n+    def set_block_value(self, request_info, result_info, block_info):","path":"lms/djangoapps/course_structure_api/v0/views.py","position":null,"original_position":299,"commit_id":"12e1d090431128dfecef6b68f6a66d9dcddef6c0","original_commit_id":"940658294142545f20268860ad2851e2d2ec0ba9","user":{"login":"nasthagiri","id":6772826,"avatar_url":"https://avatars.githubusercontent.com/u/6772826?v=3","gravatar_id":"","url":"https://api.github.com/users/nasthagiri","html_url":"https://github.com/nasthagiri","followers_url":"https://api.github.com/users/nasthagiri/followers","following_url":"https://api.github.com/users/nasthagiri/following{/other_user}","gists_url":"https://api.github.com/users/nasthagiri/gists{/gist_id}","starred_url":"https://api.github.com/users/nasthagiri/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nasthagiri/subscriptions","organizations_url":"https://api.github.com/users/nasthagiri/orgs","repos_url":"https://api.github.com/users/nasthagiri/repos","events_url":"https://api.github.com/users/nasthagiri/events{/privacy}","received_events_url":"https://api.github.com/users/nasthagiri/received_events","type":"User","site_admin":false},"body":"Yeah, I had taken a stab at refactoring the recursive method, per Cale's earlier comment.  It can be optimized even further, I'm sure.  But I've taken your suggestion to a further extent and have the block's value be set in the constructor of `BlockInfo`.","created_at":"2015-06-09T21:16:13Z","updated_at":"2015-06-09T21:16:13Z","html_url":"https://github.com/edx/edx-platform/pull/8209#discussion_r32065138","pull_request_url":"https://api.github.com/repos/edx/edx-platform/pulls/8209","_links":{"self":{"href":"https://api.github.com/repos/edx/edx-platform/pulls/comments/32065138"},"html":{"href":"https://github.com/edx/edx-platform/pull/8209#discussion_r32065138"},"pull_request":{"href":"https://api.github.com/repos/edx/edx-platform/pulls/8209"}}},"pull_request":{"url":"https://api.github.com/repos/edx/edx-platform/pulls/8209","id":36316282,"html_url":"https://github.com/edx/edx-platform/pull/8209","diff_url":"https://github.com/edx/edx-platform/pull/8209.diff","patch_url":"https://github.com/edx/edx-platform/pull/8209.patch","issue_url":"https://api.github.com/repos/edx/edx-platform/issues/8209","number":8209,"state":"open","locked":false,"title":"Course Blocks + Navigation API (feature flagged) for Mobile","user":{"login":"nasthagiri","id":6772826,"avatar_url":"https://avatars.githubusercontent.com/u/6772826?v=3","gravatar_id":"","url":"https://api.github.com/users/nasthagiri","html_url":"https://github.com/nasthagiri","followers_url":"https://api.github.com/users/nasthagiri/followers","following_url":"https://api.github.com/users/nasthagiri/following{/other_user}","gists_url":"https://api.github.com/users/nasthagiri/gists{/gist_id}","starred_url":"https://api.github.com/users/nasthagiri/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/nasthagiri/subscriptions","organizations_url":"https://api.github.com/users/nasthagiri/orgs","repos_url":"https://api.github.com/users/nasthagiri/repos","events_url":"https://api.github.com/users/nasthagiri/events{/privacy}","received_events_url":"https://api.github.com/users/nasthagiri/received_events","type":"User","site_admin":false},"body":"This PR takes a stab at providing a server-side API (behind a feature flag) to support:\r\n* custom or native rendering of user-specific course navigation\r\n* user-specific data of the course blocks\r\n* an approach for native rendering of an xBlock by introducing a `student_view_json` method, currently on the video module.\r\n\r\nIn addition, this PR:\r\n* introduces a `has_responsive_ui` property on xBlocks, currently supported by the HTML module and a subset of Capa.\r\n* moves and generalizes the `memoize_in_request_cache` decorator out of the modulestore library so it can be reused elsewhere (in particular, by the video module in this PR)\r\n\r\nThe following pending items are not part of this PR, but are planned for the upcoming weeks:\r\n* Architectural design review: https://openedx.atlassian.net/browse/MA-789\r\n* Additional unit tests for the new endpoints: https://openedx.atlassian.net/browse/MA-789\r\n* Make endpoint performant: https://openedx.atlassian.net/browse/MA-704\r\n\r\n**Reviewers:**\r\n@cpennington and/or @ormsbee \r\n@mekkz ","created_at":"2015-05-27T15:29:38Z","updated_at":"2015-06-09T21:16:13Z","closed_at":null,"merged_at":null,"merge_commit_sha":"c7e0825c5bf14ad8f186774e768f54c5e8004776","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/edx/edx-platform/pulls/8209/commits","review_comments_url":"https://api.github.com/repos/edx/edx-platform/pulls/8209/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/8209/comments","statuses_url":"https://api.github.com/repos/edx/edx-platform/statuses/12e1d090431128dfecef6b68f6a66d9dcddef6c0","head":{"label":"edx:mobile/xblock-support","ref":"mobile/xblock-support","sha":"12e1d090431128dfecef6b68f6a66d9dcddef6c0","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":"The Open edX platform, the software that powers edX!","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-06-09T17:49:18Z","pushed_at":"2015-06-09T21:15:12Z","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://open.edx.org/","size":1909422,"stargazers_count":1966,"watchers_count":1966,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":1131,"mirror_url":null,"open_issues_count":172,"forks":1131,"open_issues":172,"watchers":1966,"default_branch":"master"}},"base":{"label":"edx:master","ref":"master","sha":"7c2a4bc737b23fe34e41ec997973f3080fdd08e3","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":"The Open edX platform, the software that powers edX!","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-06-09T17:49:18Z","pushed_at":"2015-06-09T21:15:12Z","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://open.edx.org/","size":1909422,"stargazers_count":1966,"watchers_count":1966,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":1131,"mirror_url":null,"open_issues_count":172,"forks":1131,"open_issues":172,"watchers":1966,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/edx/edx-platform/pulls/8209"},"html":{"href":"https://github.com/edx/edx-platform/pull/8209"},"issue":{"href":"https://api.github.com/repos/edx/edx-platform/issues/8209"},"comments":{"href":"https://api.github.com/repos/edx/edx-platform/issues/8209/comments"},"review_comments":{"href":"https://api.github.com/repos/edx/edx-platform/pulls/8209/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/8209/commits"},"statuses":{"href":"https://api.github.com/repos/edx/edx-platform/statuses/12e1d090431128dfecef6b68f6a66d9dcddef6c0"}}}}