{"action":"created","comment":{"url":"https://api.github.com/repos/edx/edx-platform/pulls/comments/29963615","id":29963615,"diff_hunk":"@@ -0,0 +1,380 @@\n+\"\"\"\n+Certificates Data Model:\n+\n+course.certificates: {\n+    'certificates': [\n+        {\n+            'version': 1, // data contract version\n+            'id': 12345, // autogenerated identifier\n+            'name': 'Certificate 1',\n+            'description': 'Certificate 1 Description',\n+            'signatories': [\n+                {\n+                    'id': 24680, // autogenerated identifier\n+                    'name': 'Dr. Bob Smith',\n+                    'title': 'Dean of the College'\n+                }\n+            ]\n+        }\n+    ]\n+}\n+\"\"\"\n+import json\n+\n+from django.conf import settings\n+from django.contrib.auth.decorators import login_required\n+from django_future.csrf import ensure_csrf_cookie\n+from django.http import HttpResponse\n+from django.utils.translation import ugettext as _\n+from django.views.decorators.http import require_http_methods\n+\n+from contentstore.utils import reverse_course_url\n+from edxmako.shortcuts import render_to_response\n+from opaque_keys.edx.keys import CourseKey\n+from student.auth import has_studio_read_access\n+from util.db import generate_int_id, MYSQL_MAX_INT\n+from util.json_request import JsonResponse\n+from xmodule.modulestore import EdxJSONEncoder\n+from xmodule.modulestore.django import modulestore\n+from contentstore.views.exception import CertificateValidationError\n+from django.core.exceptions import PermissionDenied\n+\n+CERTIFICATE_SCHEMA_VERSION = 1\n+CERTIFICATE_MINIMUM_ID = 100\n+\n+\n+def _get_course_and_check_access(course_key, user, depth=0):\n+    \"\"\"\n+    Internal method used to calculate and return the locator and\n+    course module for the view functions in this file.\n+    \"\"\"\n+    if not has_studio_read_access(user, course_key):\n+        raise PermissionDenied()\n+    course_module = modulestore().get_course(course_key, depth=depth)\n+    return course_module\n+\n+\n+class CertificateManager(object):\n+    \"\"\"\n+    The CertificateManager is responsible for storage, retrieval, and manipulation of Certificates\n+    Certificates are not stored in the Django ORM, they are a field/setting on the course descriptor\n+    \"\"\"\n+    @staticmethod\n+    def parse(json_string):\n+        \"\"\"\n+        Deserialize the provided JSON data into a standard Python object\n+        \"\"\"\n+        try:\n+            certificate = json.loads(json_string)\n+        except ValueError:\n+            raise CertificateValidationError(_(\"invalid JSON\"))\n+        # Include the data contract version\n+        certificate[\"version\"] = CERTIFICATE_SCHEMA_VERSION\n+        # Ensure a signatories list is always returned\n+        if certificate.get(\"signatories\") is None:\n+            certificate[\"signatories\"] = []\n+        return certificate\n+\n+    @staticmethod\n+    def validate(certificate_data):\n+        \"\"\"\n+        Ensure the certificate data contains all of the necessary fields and the values match our rules\n+        \"\"\"\n+        # Ensure the schema version meets our expectations\n+        if certificate_data.get(\"version\") != CERTIFICATE_SCHEMA_VERSION:\n+            raise TypeError(\n+                \"Unsupported certificate schema version: {0}.  Expected version: {1}.\".format(\n+                    certificate_data.get(\"version\"),\n+                    CERTIFICATE_SCHEMA_VERSION\n+                )\n+            )\n+        if not certificate_data.get(\"name\"):\n+            raise CertificateValidationError(_(\"must have name of the certificate\"))\n+\n+    @staticmethod\n+    def get_used_ids(course):\n+        \"\"\"\n+        Return a list of certificate identifiers that are already in use for this course\n+        \"\"\"\n+        if not course.certificates or not course.certificates.get('certificates'):\n+            return []\n+        return [cert['id'] for cert in course.certificates['certificates']]\n+\n+    @staticmethod\n+    def assign_id(course, certificate_data, certificate_id=None):\n+        \"\"\"\n+        Assign an identifier to the provided certificate data.\n+        If the caller did not provide an identifier, we autogenerate a unique one for them\n+        In addition, we check the certificate's signatories and ensure they also have unique ids\n+        \"\"\"\n+        used_ids = CertificateManager.get_used_ids(course)\n+        if certificate_id:\n+            certificate_data['id'] = int(certificate_id)\n+        else:\n+            certificate_data['id'] = generate_int_id(\n+                CERTIFICATE_MINIMUM_ID,\n+                MYSQL_MAX_INT,\n+                used_ids\n+            )\n+\n+        for index, signatory in enumerate(certificate_data['signatories']):  # pylint: disable=unused-variable\n+            if signatory and not signatory.get('id', False):\n+                signatory['id'] = generate_int_id(used_ids=used_ids)\n+            used_ids.append(signatory['id'])\n+\n+        return certificate_data\n+\n+    @staticmethod\n+    def to_json(certificate):\n+        \"\"\"\n+        Serialize the Certificate object's locally-stored certificate data to a JSON representation\n+        We use direct access here for specific keys in order to enforce their presence\n+        \"\"\"\n+        # pylint: disable=no-member\n+        certificate_data = certificate.get_certificate_data()\n+        return {\n+            \"id\": certificate_data['id'],\n+            \"name\": certificate_data['name'],\n+            \"description\": certificate_data['description'],\n+            \"version\": CERTIFICATE_SCHEMA_VERSION,\n+            \"signatories\": certificate_data['signatories']\n+        }\n+\n+    @staticmethod\n+    def from_json(course, value):\n+        \"\"\"\n+        Deserialize from a JSON representation into a Certificate object.\n+        'value' should be either a Certificate instance, or a valid JSON string\n+        \"\"\"\n+\n+        # Ensure the schema fieldset meets our expectations\n+        for key in (\"name\", \"description\", \"version\"):\n+            if key not in value:\n+                raise CertificateValidationError(_(\"Certificate dict {0} missing value key '{1}'\").format(value, key))\n+\n+        # Load up the Certificate data\n+        certificate_data = CertificateManager.parse(value)\n+        CertificateManager.validate(certificate_data)\n+        certificate_data = CertificateManager.assign_id(course, certificate_data, certificate_data.get('id', None))\n+        certificate = Certificate(course, certificate_data)\n+\n+        # Return a new Certificate object instance\n+        return certificate\n+\n+    @staticmethod\n+    def deserialize_certificate(course, json_string):\n+        \"\"\"\n+        Returns a Certificate object instance given a valid course and JSON representation\n+        \"\"\"\n+        return CertificateManager.from_json(course, json_string)\n+\n+    @staticmethod\n+    def get_certificates(course):\n+        \"\"\"\n+        Retrieve the certificates list from the provided course\n+        \"\"\"\n+        # The top-level course field is 'certificates'\n+        course_certificates_field = getattr(course, 'certificates')\n+        # Inside of this top-level field are various things, including the actual 'certificates' list\n+        certificates = course_certificates_field.get('certificates', [])\n+        return certificates\n+\n+    @staticmethod\n+    def remove_certificate(request, store, course, certificate_id):\n+        \"\"\"\n+        Remove certificate from the course\n+        \"\"\"\n+        for index, cert in enumerate(course.certificates['certificates']):\n+            if int(cert['id']) == int(certificate_id):\n+                course.certificates['certificates'].pop(index)\n+                store.update_item(course, request.user.id)\n+                break\n+        return JsonResponse(status=204)\n+\n+    # pylint-disable: unused-variable\n+    @staticmethod\n+    def remove_signatory(request, store, course, certificate_id, signatory_id):\n+        \"\"\"\n+        Remove the specified signatory from the provided course certificate\n+        \"\"\"\n+        for cert_index, cert in enumerate(course.certificates['certificates']):  # pylint: disable=unused-variable\n+            if int(cert['id']) == int(certificate_id):\n+                for sig_index, signatory in enumerate(cert.get('signatories')):  # pylint: disable=unused-variable\n+                    if int(signatory_id) == int(signatory['id']):\n+                        del cert['signatories'][sig_index]\n+                        store.update_item(course, request.user.id)\n+                        break\n+        return JsonResponse(status=204)\n+\n+\n+class Certificate(object):\n+    \"\"\"\n+    The logical representation of an individual course certificate\n+    \"\"\"\n+    def __init__(self, course, certificate_data):\n+        \"\"\"\n+        Instantiate a Certificate object instance using the provided information.\n+        \"\"\"\n+        self.course = course\n+        self.certificate_data = certificate_data\n+        self.id = certificate_data['id']  # pylint: disable=invalid-name\n+\n+    def get_certificate_data(self):","path":"cms/djangoapps/contentstore/views/certificates.py","position":222,"original_position":222,"commit_id":"8487b2aaf9f8e3d44be58fb829299cf3a290301a","original_commit_id":"8487b2aaf9f8e3d44be58fb829299cf3a290301a","user":{"login":"martynjames","id":675165,"avatar_url":"https://avatars.githubusercontent.com/u/675165?v=3","gravatar_id":"","url":"https://api.github.com/users/martynjames","html_url":"https://github.com/martynjames","followers_url":"https://api.github.com/users/martynjames/followers","following_url":"https://api.github.com/users/martynjames/following{/other_user}","gists_url":"https://api.github.com/users/martynjames/gists{/gist_id}","starred_url":"https://api.github.com/users/martynjames/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/martynjames/subscriptions","organizations_url":"https://api.github.com/users/martynjames/orgs","repos_url":"https://api.github.com/users/martynjames/repos","events_url":"https://api.github.com/users/martynjames/events{/privacy}","received_events_url":"https://api.github.com/users/martynjames/received_events","type":"User","site_admin":false},"body":"Could make this a @property, then I think one can remove the `# pylint: disable=no-member` flag when calling above... although I'm not even sure that it's needed anyway (I don't appear to get the error using a local pylint plugin)","created_at":"2015-05-08T18:20:00Z","updated_at":"2015-05-08T18:20:00Z","html_url":"https://github.com/edx/edx-platform/pull/7882#discussion_r29963615","pull_request_url":"https://api.github.com/repos/edx/edx-platform/pulls/7882","_links":{"self":{"href":"https://api.github.com/repos/edx/edx-platform/pulls/comments/29963615"},"html":{"href":"https://github.com/edx/edx-platform/pull/7882#discussion_r29963615"},"pull_request":{"href":"https://api.github.com/repos/edx/edx-platform/pulls/7882"}}},"pull_request":{"url":"https://api.github.com/repos/edx/edx-platform/pulls/7882","id":34534600,"html_url":"https://github.com/edx/edx-platform/pull/7882","diff_url":"https://github.com/edx/edx-platform/pull/7882.diff","patch_url":"https://github.com/edx/edx-platform/pull/7882.patch","issue_url":"https://api.github.com/repos/edx/edx-platform/issues/7882","number":7882,"state":"open","locked":false,"title":"mattdrayer/SOL-465: Configure Certificate Web View via Studio","user":{"login":"mattdrayer","id":4520595,"avatar_url":"https://avatars.githubusercontent.com/u/4520595?v=3","gravatar_id":"","url":"https://api.github.com/users/mattdrayer","html_url":"https://github.com/mattdrayer","followers_url":"https://api.github.com/users/mattdrayer/followers","following_url":"https://api.github.com/users/mattdrayer/following{/other_user}","gists_url":"https://api.github.com/users/mattdrayer/gists{/gist_id}","starred_url":"https://api.github.com/users/mattdrayer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/mattdrayer/subscriptions","organizations_url":"https://api.github.com/users/mattdrayer/orgs","repos_url":"https://api.github.com/users/mattdrayer/repos","events_url":"https://api.github.com/users/mattdrayer/events{/privacy}","received_events_url":"https://api.github.com/users/mattdrayer/received_events","type":"User","site_admin":false},"body":"JIRA:  https://openedx.atlassian.net/browse/SOL-465\r\n\r\nSandboxes:\r\n  * LMS \r\n    * http://pr7882.m.sandbox.edx.org  (log in as 'honor' user)\r\n    * http://pr7882.m.sandbox.edx.org/certificates/html?course_id=edx/DemoX/DemoX_Course\r\n  * Studio \r\n    * http://studio.pr7882.m.sandbox.edx.org (log in as 'staff' user)\r\n    * Select Course > Settings > Certificates","created_at":"2015-05-01T12:24:16Z","updated_at":"2015-05-08T18:20:00Z","closed_at":null,"merged_at":null,"merge_commit_sha":"1fe913c4900e0fd6c7f64ccd4e9c1cccf5577ab0","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/edx/edx-platform/pulls/7882/commits","review_comments_url":"https://api.github.com/repos/edx/edx-platform/pulls/7882/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/7882/comments","statuses_url":"https://api.github.com/repos/edx/edx-platform/statuses/8487b2aaf9f8e3d44be58fb829299cf3a290301a","head":{"label":"edx:mattdrayer/SOL-465","ref":"mattdrayer/SOL-465","sha":"8487b2aaf9f8e3d44be58fb829299cf3a290301a","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-08T18:19:26Z","pushed_at":"2015-05-08T18:19:27Z","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":1924658,"stargazers_count":1911,"watchers_count":1911,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":1085,"mirror_url":null,"open_issues_count":164,"forks":1085,"open_issues":164,"watchers":1911,"default_branch":"master"}},"base":{"label":"edx:master","ref":"master","sha":"2b6ad20fa6a35c7b06d796ab30221205fd19489c","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-08T18:19:26Z","pushed_at":"2015-05-08T18:19:27Z","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":1924658,"stargazers_count":1911,"watchers_count":1911,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":1085,"mirror_url":null,"open_issues_count":164,"forks":1085,"open_issues":164,"watchers":1911,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/edx/edx-platform/pulls/7882"},"html":{"href":"https://github.com/edx/edx-platform/pull/7882"},"issue":{"href":"https://api.github.com/repos/edx/edx-platform/issues/7882"},"comments":{"href":"https://api.github.com/repos/edx/edx-platform/issues/7882/comments"},"review_comments":{"href":"https://api.github.com/repos/edx/edx-platform/pulls/7882/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/7882/commits"},"statuses":{"href":"https://api.github.com/repos/edx/edx-platform/statuses/8487b2aaf9f8e3d44be58fb829299cf3a290301a"}}}}