{"action":"created","comment":{"url":"https://api.github.com/repos/CTPUG/wafer/pulls/comments/51324073","id":51324073,"diff_hunk":"@@ -0,0 +1,252 @@\n+# -*- coding: utf-8 -*-\n+# (c) 2015-16 martin f. krafft <madduck@debconf.org>\n+# Released under the terms of the same licence as Wafer.\n+\n+from django.db import models\n+from django.db.models import signals\n+from django.core.exceptions import SuspiciousOperation, ValidationError\n+from django.contrib.contenttypes.models import ContentType\n+from django.utils.encoding import python_2_unicode_compatible\n+\n+from django.conf import settings\n+KEYNAME_MAXLEN = settings.WAFER_KVPAIRS_KEYNAME_MAXLEN\n+VALUE_MAXLEN = settings.WAFER_KVPAIRS_VALUE_MAXLEN\n+AUTOCREATE_KEYS = settings.WAFER_KVPAIRS_AUTOCREATE_KEYS\n+\n+from .fields import IndirectGenericForeignKey\n+\n+\n+# the list of models to which keys can be \"attached\", not sure why we limit\n+# it, but it feels right. This feeds the choices parameter of the Key.model_ct\n+# field, and can be disabled right here…\n+SUPPORTED_MODEL_TYPES = ('sites.site', 'auth.user', 'users.userprofile',\n+        'pages.page', 'schedule.venue', 'schedule.scheduleitem', 'talks.talk',\n+        'sponsors.sponsorshippackages', 'sponsors.sponsor',\n+        'tickets.tickettype', 'tickets.ticket')\n+\n+\n+@python_2_unicode_compatible\n+class Key(models.Model):\n+    \"\"\"Definition of a specific key related to another model\"\"\"\n+\n+    # Key ownership, which controls who can access/modify the values\n+    owner = models.ForeignKey(settings.AUTH_USER_MODEL,\n+            on_delete=models.PROTECT, # could be SET(get_sentinel_user)…\n+            verbose_name='Key owner')\n+    group = models.ForeignKey('auth.Group', null=True, # groups are not mandatory\n+            on_delete=models.SET_NULL,\n+            verbose_name='Key owner group')\n+\n+    # The contenttypes framework already has all the models, let's link to\n+    # that:\n+    def _get_supported_models_as_q():\n+        label_model_pairs = [s.split('.') for s in SUPPORTED_MODEL_TYPES]\n+        if not label_model_pairs:\n+            return models.Q()\n+\n+        acc = None\n+        for a,m in label_model_pairs:\n+            qpair = models.Q(app_label=a) & models.Q(model=m)\n+            acc = qpair if acc is None else acc | qpair\n+        return acc\n+    model_ct = models.ForeignKey('contenttypes.ContentType',\n+            on_delete=models.CASCADE, verbose_name='Referenced model type',\n+            limit_choices_to=_get_supported_models_as_q)\n+\n+    # Finally, the name of the key/attribute\n+    name = models.CharField(max_length=KEYNAME_MAXLEN,\n+            verbose_name='Key name')\n+\n+    class Meta:\n+        unique_together = (('model_ct', 'name'),)\n+        verbose_name = 'Key'\n+\n+    def _check_model_is_supported(self, model_ct):\n+        \"\"\"\n+        Check that we support/allow creating keys for the given model type,\n+        which cannot be done with choices on the ForeignKey as ContentTypes\n+        aren't ready at the stage of class definition yet.\n+        \"\"\"\n+        if model_ct.model_class() not in SUPPORTED_MODEL_CLASSES:\n+            raise UnsupportedModelForKey('No support to create Keys '\n+                    'for model: %s' %\n+                        self._format_model_name(model_ct.app_label,\n+                            model_ct.model))\n+        return True\n+\n+    def __init__(self, *args, **kwargs):\n+        \"\"\"\n+        Initialise a Key() instance. This can be done in three ways, depending\n+        on which arguments are passed:\n+\n+        - model_class: a class object, e.g. Talk\n+        - model_ct: a ContentType instance for a model\n+        - model, app_label: strings describing a ContentType\n+        \"\"\"\n+        if 'model_class' in kwargs:\n+            self._init_from_model_class(*args, **kwargs)\n+        elif 'model' in kwargs and 'app_label' in kwargs:\n+            self._init_from_ct_app_label_and_model(*args, **kwargs)\n+        else:\n+            self._init_from_model_ct(*args, **kwargs)\n+\n+    def _init_from_model_class(self, *args, **kwargs):\n+        ct = ContentType.objects.get_for_model(kwargs.pop('model_class'))\n+        self._init_from_model_ct(model_ct=ct, *args, **kwargs)\n+\n+    def _init_from_ct_app_label_and_model(self, *args, **kwargs):\n+        ct = ContentType.objects.get(app_label=kwargs.pop('app_label'),\n+                model=kwargs.pop('model'))\n+        self._init_from_model_ct(model_ct=ct, *args, **kwargs)\n+\n+    def _init_from_model_ct(self, *args, **kwargs):\n+        super(Key, self).__init__(*args, **kwargs)\n+\n+    @classmethod\n+    def _format_model_name(cls, app_label, model):\n+        return '.'.join((app_label, model))\n+\n+    @classmethod\n+    def _format_model_class(cls, model_class):\n+        ct = ContentType.objects.get_for_model(model_class)\n+        return _format_model_name(ct.app_label, ct.model)\n+\n+    def get_model_class(self):\n+        '''Return the class object of the model to which this key relates'''\n+        return self.model_ct.model_class()\n+\n+    def get_model_name(self):\n+        '''Return the name of the model to which this Key is attached'''\n+        return self._format_model_name(self.model_ct.app_label,\n+                self.model_ct.model)\n+    # For direct use of this attribute in forms:\n+    get_model_name.short_description = model_ct.verbose_name\n+    get_model_name.admin_order_field = 'model_ct'\n+\n+    def get_fully_qualified_key_name(self, quoted=False):\n+        '''Return the name of the key properly namespaced'''\n+        name = self.name.join((\"'\",)*2) if quoted else self.name\n+        return '/'.join((self.get_model_name(), name))\n+    # For direct use of this attribute in forms:\n+    get_fully_qualified_key_name.short_description = name.verbose_name\n+    get_fully_qualified_key_name.admin_order_field = 'name'\n+\n+    def is_compatible_instance(self, instance):\n+        '''Return whether a given instance is compatible with the associated model'''\n+        return isinstance(instance, self.get_model_class())\n+\n+    def natural_key(self):\n+        '''Return the natural key of this Key (for serialization)'''\n+        return self.model_ct.natural_key() + (self.name,)\n+\n+    def __str__(self):\n+        '''Return an informative text display string for this Key'''\n+        return u\"[%s] %s\" % (\n+                'id:%s'%self.id if self.id else 'unsaved',\n+                self.get_fully_qualified_key_name(quoted=True))\n+\n+\n+@python_2_unicode_compatible\n+class KeyValuePair(models.Model):\n+    \"\"\"Mapping of referenced model instances to values\"\"\"\n+\n+    # The key definition to use\n+    key = models.ForeignKey('Key', on_delete=models.PROTECT,\n+            verbose_name='Base key context')\n+\n+    # The foreign key to the actual object we're referencing, e.g. the User to\n+    # which we are attributing a value\n+    ref_id = models.PositiveIntegerField(\n+            verbose_name='ID of referenced model instance')\n+\n+    # The logic for the indirect generic foreign key happens in the\n+    # IndirectGenericForeignKey field, which we need to instantiate, but it\n+    # doesn't actually hold any data.\n+    ref_obj = IndirectGenericForeignKey(ct_field_path='key.model_ct',\n+            fk_field='ref_id')\n+\n+    # finally the value to store\n+    value = models.CharField(max_length=VALUE_MAXLEN,\n+            verbose_name='Value for key associated with model instance')\n+\n+    class Meta:\n+        unique_together = (('key', 'ref_id'),)\n+        verbose_name = 'Key-value pair'\n+\n+    def __str__(self):\n+        '''Return an informative text display string for this KeyValuePair'''\n+        return u'[%s] %s(%s)/\\'%s\\'=\"%s\"' % (\n+                'id:%s'%self.id if self.id else 'unsaved',\n+                self.key.get_model_name(), self.get_ref_instance(),\n+                self.key.name, self.value)\n+\n+    def get_model_class(self):\n+        '''Returns the model class associated with the Key'''\n+        return self.key.get_model_class()\n+\n+    def get_ref_instance(self):\n+        '''Returns the instance referenced by the KeyValuePair'''\n+        model_class = self.get_model_class()\n+        return model_class.objects.get(pk=self.ref_id)","path":"wafer/kvpairs/models.py","position":190,"original_position":190,"commit_id":"9362f9d83a1072596a16a54f3161997ea03f89d0","original_commit_id":"9362f9d83a1072596a16a54f3161997ea03f89d0","user":{"login":"drnlm","id":642056,"avatar_url":"https://avatars.githubusercontent.com/u/642056?v=3","gravatar_id":"","url":"https://api.github.com/users/drnlm","html_url":"https://github.com/drnlm","followers_url":"https://api.github.com/users/drnlm/followers","following_url":"https://api.github.com/users/drnlm/following{/other_user}","gists_url":"https://api.github.com/users/drnlm/gists{/gist_id}","starred_url":"https://api.github.com/users/drnlm/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/drnlm/subscriptions","organizations_url":"https://api.github.com/users/drnlm/orgs","repos_url":"https://api.github.com/users/drnlm/repos","events_url":"https://api.github.com/users/drnlm/events{/privacy}","received_events_url":"https://api.github.com/users/drnlm/received_events","type":"User","site_admin":false},"body":"This looks a lookup that could be cached - we don't expect the referenced object to change and this seems like it will be called reasonably often if working with the value pairs.","created_at":"2016-01-29T22:18:34Z","updated_at":"2016-01-29T22:18:34Z","html_url":"https://github.com/CTPUG/wafer/pull/186#discussion_r51324073","pull_request_url":"https://api.github.com/repos/CTPUG/wafer/pulls/186","_links":{"self":{"href":"https://api.github.com/repos/CTPUG/wafer/pulls/comments/51324073"},"html":{"href":"https://github.com/CTPUG/wafer/pull/186#discussion_r51324073"},"pull_request":{"href":"https://api.github.com/repos/CTPUG/wafer/pulls/186"}}},"pull_request":{"url":"https://api.github.com/repos/CTPUG/wafer/pulls/186","id":55845357,"html_url":"https://github.com/CTPUG/wafer/pull/186","diff_url":"https://github.com/CTPUG/wafer/pull/186.diff","patch_url":"https://github.com/CTPUG/wafer/pull/186.patch","issue_url":"https://api.github.com/repos/CTPUG/wafer/issues/186","number":186,"state":"open","locked":false,"title":"147 kvpairs","user":{"login":"madduck","id":195073,"avatar_url":"https://avatars.githubusercontent.com/u/195073?v=3","gravatar_id":"","url":"https://api.github.com/users/madduck","html_url":"https://github.com/madduck","followers_url":"https://api.github.com/users/madduck/followers","following_url":"https://api.github.com/users/madduck/following{/other_user}","gists_url":"https://api.github.com/users/madduck/gists{/gist_id}","starred_url":"https://api.github.com/users/madduck/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/madduck/subscriptions","organizations_url":"https://api.github.com/users/madduck/orgs","repos_url":"https://api.github.com/users/madduck/repos","events_url":"https://api.github.com/users/madduck/events{/privacy}","received_events_url":"https://api.github.com/users/madduck/received_events","type":"User","site_admin":false},"body":"While there are still things left to do (cf. `docs/kvpairs.rst`), this is ready for merging. The licence is now the same as Wafer's. Please refer to #147 and the aforementioned documentation for more information. The code itself has also been documented.\r\n\r\nCloses: #147","created_at":"2016-01-13T01:58:49Z","updated_at":"2016-01-29T22:18:35Z","closed_at":null,"merged_at":null,"merge_commit_sha":"391c042814507f91c794ad0db06acd150a4301a4","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/CTPUG/wafer/pulls/186/commits","review_comments_url":"https://api.github.com/repos/CTPUG/wafer/pulls/186/comments","review_comment_url":"https://api.github.com/repos/CTPUG/wafer/pulls/comments{/number}","comments_url":"https://api.github.com/repos/CTPUG/wafer/issues/186/comments","statuses_url":"https://api.github.com/repos/CTPUG/wafer/statuses/9362f9d83a1072596a16a54f3161997ea03f89d0","head":{"label":"madduck:147-kvpairs","ref":"147-kvpairs","sha":"9362f9d83a1072596a16a54f3161997ea03f89d0","user":{"login":"madduck","id":195073,"avatar_url":"https://avatars.githubusercontent.com/u/195073?v=3","gravatar_id":"","url":"https://api.github.com/users/madduck","html_url":"https://github.com/madduck","followers_url":"https://api.github.com/users/madduck/followers","following_url":"https://api.github.com/users/madduck/following{/other_user}","gists_url":"https://api.github.com/users/madduck/gists{/gist_id}","starred_url":"https://api.github.com/users/madduck/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/madduck/subscriptions","organizations_url":"https://api.github.com/users/madduck/orgs","repos_url":"https://api.github.com/users/madduck/repos","events_url":"https://api.github.com/users/madduck/events{/privacy}","received_events_url":"https://api.github.com/users/madduck/received_events","type":"User","site_admin":false},"repo":{"id":46833484,"name":"wafer","full_name":"madduck/wafer","owner":{"login":"madduck","id":195073,"avatar_url":"https://avatars.githubusercontent.com/u/195073?v=3","gravatar_id":"","url":"https://api.github.com/users/madduck","html_url":"https://github.com/madduck","followers_url":"https://api.github.com/users/madduck/followers","following_url":"https://api.github.com/users/madduck/following{/other_user}","gists_url":"https://api.github.com/users/madduck/gists{/gist_id}","starred_url":"https://api.github.com/users/madduck/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/madduck/subscriptions","organizations_url":"https://api.github.com/users/madduck/orgs","repos_url":"https://api.github.com/users/madduck/repos","events_url":"https://api.github.com/users/madduck/events{/privacy}","received_events_url":"https://api.github.com/users/madduck/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/madduck/wafer","description":"A wafer-thin web application for running small conferences. Built using Django.","fork":true,"url":"https://api.github.com/repos/madduck/wafer","forks_url":"https://api.github.com/repos/madduck/wafer/forks","keys_url":"https://api.github.com/repos/madduck/wafer/keys{/key_id}","collaborators_url":"https://api.github.com/repos/madduck/wafer/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/madduck/wafer/teams","hooks_url":"https://api.github.com/repos/madduck/wafer/hooks","issue_events_url":"https://api.github.com/repos/madduck/wafer/issues/events{/number}","events_url":"https://api.github.com/repos/madduck/wafer/events","assignees_url":"https://api.github.com/repos/madduck/wafer/assignees{/user}","branches_url":"https://api.github.com/repos/madduck/wafer/branches{/branch}","tags_url":"https://api.github.com/repos/madduck/wafer/tags","blobs_url":"https://api.github.com/repos/madduck/wafer/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/madduck/wafer/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/madduck/wafer/git/refs{/sha}","trees_url":"https://api.github.com/repos/madduck/wafer/git/trees{/sha}","statuses_url":"https://api.github.com/repos/madduck/wafer/statuses/{sha}","languages_url":"https://api.github.com/repos/madduck/wafer/languages","stargazers_url":"https://api.github.com/repos/madduck/wafer/stargazers","contributors_url":"https://api.github.com/repos/madduck/wafer/contributors","subscribers_url":"https://api.github.com/repos/madduck/wafer/subscribers","subscription_url":"https://api.github.com/repos/madduck/wafer/subscription","commits_url":"https://api.github.com/repos/madduck/wafer/commits{/sha}","git_commits_url":"https://api.github.com/repos/madduck/wafer/git/commits{/sha}","comments_url":"https://api.github.com/repos/madduck/wafer/comments{/number}","issue_comment_url":"https://api.github.com/repos/madduck/wafer/issues/comments{/number}","contents_url":"https://api.github.com/repos/madduck/wafer/contents/{+path}","compare_url":"https://api.github.com/repos/madduck/wafer/compare/{base}...{head}","merges_url":"https://api.github.com/repos/madduck/wafer/merges","archive_url":"https://api.github.com/repos/madduck/wafer/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/madduck/wafer/downloads","issues_url":"https://api.github.com/repos/madduck/wafer/issues{/number}","pulls_url":"https://api.github.com/repos/madduck/wafer/pulls{/number}","milestones_url":"https://api.github.com/repos/madduck/wafer/milestones{/number}","notifications_url":"https://api.github.com/repos/madduck/wafer/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/madduck/wafer/labels{/name}","releases_url":"https://api.github.com/repos/madduck/wafer/releases{/id}","deployments_url":"https://api.github.com/repos/madduck/wafer/deployments","created_at":"2015-11-25T02:53:29Z","updated_at":"2016-01-14T04:24:23Z","pushed_at":"2016-01-16T08:25:35Z","git_url":"git://github.com/madduck/wafer.git","ssh_url":"git@github.com:madduck/wafer.git","clone_url":"https://github.com/madduck/wafer.git","svn_url":"https://github.com/madduck/wafer","homepage":null,"size":1029,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"CTPUG:master","ref":"master","sha":"509b3975a539ee15aceebfa121f9c9280a2686fe","user":{"login":"CTPUG","id":647522,"avatar_url":"https://avatars.githubusercontent.com/u/647522?v=3","gravatar_id":"","url":"https://api.github.com/users/CTPUG","html_url":"https://github.com/CTPUG","followers_url":"https://api.github.com/users/CTPUG/followers","following_url":"https://api.github.com/users/CTPUG/following{/other_user}","gists_url":"https://api.github.com/users/CTPUG/gists{/gist_id}","starred_url":"https://api.github.com/users/CTPUG/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/CTPUG/subscriptions","organizations_url":"https://api.github.com/users/CTPUG/orgs","repos_url":"https://api.github.com/users/CTPUG/repos","events_url":"https://api.github.com/users/CTPUG/events{/privacy}","received_events_url":"https://api.github.com/users/CTPUG/received_events","type":"Organization","site_admin":false},"repo":{"id":8658619,"name":"wafer","full_name":"CTPUG/wafer","owner":{"login":"CTPUG","id":647522,"avatar_url":"https://avatars.githubusercontent.com/u/647522?v=3","gravatar_id":"","url":"https://api.github.com/users/CTPUG","html_url":"https://github.com/CTPUG","followers_url":"https://api.github.com/users/CTPUG/followers","following_url":"https://api.github.com/users/CTPUG/following{/other_user}","gists_url":"https://api.github.com/users/CTPUG/gists{/gist_id}","starred_url":"https://api.github.com/users/CTPUG/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/CTPUG/subscriptions","organizations_url":"https://api.github.com/users/CTPUG/orgs","repos_url":"https://api.github.com/users/CTPUG/repos","events_url":"https://api.github.com/users/CTPUG/events{/privacy}","received_events_url":"https://api.github.com/users/CTPUG/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/CTPUG/wafer","description":"A wafer-thin web application for running small conferences. Built using Django.","fork":false,"url":"https://api.github.com/repos/CTPUG/wafer","forks_url":"https://api.github.com/repos/CTPUG/wafer/forks","keys_url":"https://api.github.com/repos/CTPUG/wafer/keys{/key_id}","collaborators_url":"https://api.github.com/repos/CTPUG/wafer/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/CTPUG/wafer/teams","hooks_url":"https://api.github.com/repos/CTPUG/wafer/hooks","issue_events_url":"https://api.github.com/repos/CTPUG/wafer/issues/events{/number}","events_url":"https://api.github.com/repos/CTPUG/wafer/events","assignees_url":"https://api.github.com/repos/CTPUG/wafer/assignees{/user}","branches_url":"https://api.github.com/repos/CTPUG/wafer/branches{/branch}","tags_url":"https://api.github.com/repos/CTPUG/wafer/tags","blobs_url":"https://api.github.com/repos/CTPUG/wafer/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/CTPUG/wafer/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/CTPUG/wafer/git/refs{/sha}","trees_url":"https://api.github.com/repos/CTPUG/wafer/git/trees{/sha}","statuses_url":"https://api.github.com/repos/CTPUG/wafer/statuses/{sha}","languages_url":"https://api.github.com/repos/CTPUG/wafer/languages","stargazers_url":"https://api.github.com/repos/CTPUG/wafer/stargazers","contributors_url":"https://api.github.com/repos/CTPUG/wafer/contributors","subscribers_url":"https://api.github.com/repos/CTPUG/wafer/subscribers","subscription_url":"https://api.github.com/repos/CTPUG/wafer/subscription","commits_url":"https://api.github.com/repos/CTPUG/wafer/commits{/sha}","git_commits_url":"https://api.github.com/repos/CTPUG/wafer/git/commits{/sha}","comments_url":"https://api.github.com/repos/CTPUG/wafer/comments{/number}","issue_comment_url":"https://api.github.com/repos/CTPUG/wafer/issues/comments{/number}","contents_url":"https://api.github.com/repos/CTPUG/wafer/contents/{+path}","compare_url":"https://api.github.com/repos/CTPUG/wafer/compare/{base}...{head}","merges_url":"https://api.github.com/repos/CTPUG/wafer/merges","archive_url":"https://api.github.com/repos/CTPUG/wafer/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/CTPUG/wafer/downloads","issues_url":"https://api.github.com/repos/CTPUG/wafer/issues{/number}","pulls_url":"https://api.github.com/repos/CTPUG/wafer/pulls{/number}","milestones_url":"https://api.github.com/repos/CTPUG/wafer/milestones{/number}","notifications_url":"https://api.github.com/repos/CTPUG/wafer/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/CTPUG/wafer/labels{/name}","releases_url":"https://api.github.com/repos/CTPUG/wafer/releases{/id}","deployments_url":"https://api.github.com/repos/CTPUG/wafer/deployments","created_at":"2013-03-08T19:50:58Z","updated_at":"2016-01-14T16:21:23Z","pushed_at":"2016-01-24T14:39:06Z","git_url":"git://github.com/CTPUG/wafer.git","ssh_url":"git@github.com:CTPUG/wafer.git","clone_url":"https://github.com/CTPUG/wafer.git","svn_url":"https://github.com/CTPUG/wafer","homepage":null,"size":1041,"stargazers_count":6,"watchers_count":6,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":8,"mirror_url":null,"open_issues_count":26,"forks":8,"open_issues":26,"watchers":6,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/CTPUG/wafer/pulls/186"},"html":{"href":"https://github.com/CTPUG/wafer/pull/186"},"issue":{"href":"https://api.github.com/repos/CTPUG/wafer/issues/186"},"comments":{"href":"https://api.github.com/repos/CTPUG/wafer/issues/186/comments"},"review_comments":{"href":"https://api.github.com/repos/CTPUG/wafer/pulls/186/comments"},"review_comment":{"href":"https://api.github.com/repos/CTPUG/wafer/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/CTPUG/wafer/pulls/186/commits"},"statuses":{"href":"https://api.github.com/repos/CTPUG/wafer/statuses/9362f9d83a1072596a16a54f3161997ea03f89d0"}}}}