{"action":"created","comment":{"url":"https://api.github.com/repos/hypothesis/h/pulls/comments/62827681","id":62827681,"diff_hunk":"@@ -105,3 +112,57 @@ def remove_old_flags(cls):\n \n     def __repr__(self):\n         return '<Feature {f.name} everyone={f.everyone}>'.format(f=self)\n+\n+\n+class Cohort(Base, mixins.Timestamps):\n+    __tablename__ = 'cohort'\n+\n+    id = sa.Column(sa.Integer, autoincrement=True, primary_key=True)\n+    name = sa.Column(sa.UnicodeText(), nullable=False, index=True)\n+\n+    # Cohort membership\n+    members = sa.orm.relationship(\n+        'User', secondary='user_cohort', backref=sa.orm.backref(\n+            'cohorts', order_by='Cohort.name'))\n+\n+    def __init__(self, name, creator):\n+        self.name = name\n+        self.creator = creator\n+        self.members.append(creator)\n+\n+    @sa.orm.validates('name')\n+    def validate_name(self, key, name):\n+        if not COHORT_NAME_MIN_LENGTH <= len(name) <= COHORT_NAME_MAX_LENGTH:\n+            raise ValueError('name must be between {min} and {max} characters '\n+                             'long'.format(min=COHORT_NAME_MIN_LENGTH,\n+                                           max=COHORT_NAME_MAX_LENGTH))\n+        return name\n+\n+    def __repr__(self):\n+        return '<Cohort: %s>' % self.slug\n+\n+    @property\n+    def slug(self):\n+        \"\"\"A version of this group's name suitable for use in a URL.\"\"\"\n+        return slugify.slugify(self.name)\n+\n+    @classmethod\n+    def get_by_id(cls, id_):\n+        \"\"\"Return the cohort with the given id, or None.\"\"\"\n+        try:\n+            return cls.query.filter(cls.id == id_).one()\n+        except exc.NoResultFound:\n+            return None\n+\n+\n+USER_COHORT_TABLE = sa.Table(","path":"h/features/models.py","position":72,"original_position":72,"commit_id":"1d715b1c215c6fbe1272fdbaeb8737bb5110c885","original_commit_id":"1d715b1c215c6fbe1272fdbaeb8737bb5110c885","user":{"login":"finitud","id":209716,"avatar_url":"https://avatars.githubusercontent.com/u/209716?v=3","gravatar_id":"","url":"https://api.github.com/users/finitud","html_url":"https://github.com/finitud","followers_url":"https://api.github.com/users/finitud/followers","following_url":"https://api.github.com/users/finitud/following{/other_user}","gists_url":"https://api.github.com/users/finitud/gists{/gist_id}","starred_url":"https://api.github.com/users/finitud/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/finitud/subscriptions","organizations_url":"https://api.github.com/users/finitud/orgs","repos_url":"https://api.github.com/users/finitud/repos","events_url":"https://api.github.com/users/finitud/events{/privacy}","received_events_url":"https://api.github.com/users/finitud/received_events","type":"User","site_admin":false},"body":"Is this `USER_COHORT_TABLE` variable here necessary, now that I look at it again?","created_at":"2016-05-11T10:53:43Z","updated_at":"2016-05-11T10:53:43Z","html_url":"https://github.com/hypothesis/h/pull/3309#discussion_r62827681","pull_request_url":"https://api.github.com/repos/hypothesis/h/pulls/3309","_links":{"self":{"href":"https://api.github.com/repos/hypothesis/h/pulls/comments/62827681"},"html":{"href":"https://github.com/hypothesis/h/pull/3309#discussion_r62827681"},"pull_request":{"href":"https://api.github.com/repos/hypothesis/h/pulls/3309"}}},"pull_request":{"url":"https://api.github.com/repos/hypothesis/h/pulls/3309","id":69593531,"html_url":"https://github.com/hypothesis/h/pull/3309","diff_url":"https://github.com/hypothesis/h/pull/3309.diff","patch_url":"https://github.com/hypothesis/h/pull/3309.patch","issue_url":"https://api.github.com/repos/hypothesis/h/issues/3309","number":3309,"state":"open","locked":false,"title":"Feature flags cohorts: Add Cohort model","user":{"login":"finitud","id":209716,"avatar_url":"https://avatars.githubusercontent.com/u/209716?v=3","gravatar_id":"","url":"https://api.github.com/users/finitud","html_url":"https://github.com/finitud","followers_url":"https://api.github.com/users/finitud/followers","following_url":"https://api.github.com/users/finitud/following{/other_user}","gists_url":"https://api.github.com/users/finitud/gists{/gist_id}","starred_url":"https://api.github.com/users/finitud/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/finitud/subscriptions","organizations_url":"https://api.github.com/users/finitud/orgs","repos_url":"https://api.github.com/users/finitud/repos","events_url":"https://api.github.com/users/finitud/events{/privacy}","received_events_url":"https://api.github.com/users/finitud/received_events","type":"User","site_admin":false},"body":"This is the first of the pull requests referred to in #3302. It defines the `Cohort` model on `h.features.models`.","created_at":"2016-05-10T23:21:37Z","updated_at":"2016-05-11T10:53:43Z","closed_at":null,"merged_at":null,"merge_commit_sha":"f47c2754f193f0d36f0ad3587bbeb27c992cd8e4","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/hypothesis/h/pulls/3309/commits","review_comments_url":"https://api.github.com/repos/hypothesis/h/pulls/3309/comments","review_comment_url":"https://api.github.com/repos/hypothesis/h/pulls/comments{/number}","comments_url":"https://api.github.com/repos/hypothesis/h/issues/3309/comments","statuses_url":"https://api.github.com/repos/hypothesis/h/statuses/1d715b1c215c6fbe1272fdbaeb8737bb5110c885","head":{"label":"hypothesis:wyan/feature-cohorts-model","ref":"wyan/feature-cohorts-model","sha":"1d715b1c215c6fbe1272fdbaeb8737bb5110c885","user":{"login":"hypothesis","id":1243215,"avatar_url":"https://avatars.githubusercontent.com/u/1243215?v=3","gravatar_id":"","url":"https://api.github.com/users/hypothesis","html_url":"https://github.com/hypothesis","followers_url":"https://api.github.com/users/hypothesis/followers","following_url":"https://api.github.com/users/hypothesis/following{/other_user}","gists_url":"https://api.github.com/users/hypothesis/gists{/gist_id}","starred_url":"https://api.github.com/users/hypothesis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hypothesis/subscriptions","organizations_url":"https://api.github.com/users/hypothesis/orgs","repos_url":"https://api.github.com/users/hypothesis/repos","events_url":"https://api.github.com/users/hypothesis/events{/privacy}","received_events_url":"https://api.github.com/users/hypothesis/received_events","type":"Organization","site_admin":false},"repo":{"id":3910945,"name":"h","full_name":"hypothesis/h","owner":{"login":"hypothesis","id":1243215,"avatar_url":"https://avatars.githubusercontent.com/u/1243215?v=3","gravatar_id":"","url":"https://api.github.com/users/hypothesis","html_url":"https://github.com/hypothesis","followers_url":"https://api.github.com/users/hypothesis/followers","following_url":"https://api.github.com/users/hypothesis/following{/other_user}","gists_url":"https://api.github.com/users/hypothesis/gists{/gist_id}","starred_url":"https://api.github.com/users/hypothesis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hypothesis/subscriptions","organizations_url":"https://api.github.com/users/hypothesis/orgs","repos_url":"https://api.github.com/users/hypothesis/repos","events_url":"https://api.github.com/users/hypothesis/events{/privacy}","received_events_url":"https://api.github.com/users/hypothesis/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/hypothesis/h","description":"The Internet, peer reviewed.","fork":false,"url":"https://api.github.com/repos/hypothesis/h","forks_url":"https://api.github.com/repos/hypothesis/h/forks","keys_url":"https://api.github.com/repos/hypothesis/h/keys{/key_id}","collaborators_url":"https://api.github.com/repos/hypothesis/h/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/hypothesis/h/teams","hooks_url":"https://api.github.com/repos/hypothesis/h/hooks","issue_events_url":"https://api.github.com/repos/hypothesis/h/issues/events{/number}","events_url":"https://api.github.com/repos/hypothesis/h/events","assignees_url":"https://api.github.com/repos/hypothesis/h/assignees{/user}","branches_url":"https://api.github.com/repos/hypothesis/h/branches{/branch}","tags_url":"https://api.github.com/repos/hypothesis/h/tags","blobs_url":"https://api.github.com/repos/hypothesis/h/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/hypothesis/h/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/hypothesis/h/git/refs{/sha}","trees_url":"https://api.github.com/repos/hypothesis/h/git/trees{/sha}","statuses_url":"https://api.github.com/repos/hypothesis/h/statuses/{sha}","languages_url":"https://api.github.com/repos/hypothesis/h/languages","stargazers_url":"https://api.github.com/repos/hypothesis/h/stargazers","contributors_url":"https://api.github.com/repos/hypothesis/h/contributors","subscribers_url":"https://api.github.com/repos/hypothesis/h/subscribers","subscription_url":"https://api.github.com/repos/hypothesis/h/subscription","commits_url":"https://api.github.com/repos/hypothesis/h/commits{/sha}","git_commits_url":"https://api.github.com/repos/hypothesis/h/git/commits{/sha}","comments_url":"https://api.github.com/repos/hypothesis/h/comments{/number}","issue_comment_url":"https://api.github.com/repos/hypothesis/h/issues/comments{/number}","contents_url":"https://api.github.com/repos/hypothesis/h/contents/{+path}","compare_url":"https://api.github.com/repos/hypothesis/h/compare/{base}...{head}","merges_url":"https://api.github.com/repos/hypothesis/h/merges","archive_url":"https://api.github.com/repos/hypothesis/h/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/hypothesis/h/downloads","issues_url":"https://api.github.com/repos/hypothesis/h/issues{/number}","pulls_url":"https://api.github.com/repos/hypothesis/h/pulls{/number}","milestones_url":"https://api.github.com/repos/hypothesis/h/milestones{/number}","notifications_url":"https://api.github.com/repos/hypothesis/h/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/hypothesis/h/labels{/name}","releases_url":"https://api.github.com/repos/hypothesis/h/releases{/id}","deployments_url":"https://api.github.com/repos/hypothesis/h/deployments","created_at":"2012-04-02T19:56:59Z","updated_at":"2016-05-08T02:27:46Z","pushed_at":"2016-05-11T10:37:40Z","git_url":"git://github.com/hypothesis/h.git","ssh_url":"git@github.com:hypothesis/h.git","clone_url":"https://github.com/hypothesis/h.git","svn_url":"https://github.com/hypothesis/h","homepage":"https://hypothes.is/","size":28457,"stargazers_count":632,"watchers_count":632,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":148,"mirror_url":null,"open_issues_count":70,"forks":148,"open_issues":70,"watchers":632,"default_branch":"master"}},"base":{"label":"hypothesis:master","ref":"master","sha":"3fa2825228a929d5810765b98e1ace07990fe9d3","user":{"login":"hypothesis","id":1243215,"avatar_url":"https://avatars.githubusercontent.com/u/1243215?v=3","gravatar_id":"","url":"https://api.github.com/users/hypothesis","html_url":"https://github.com/hypothesis","followers_url":"https://api.github.com/users/hypothesis/followers","following_url":"https://api.github.com/users/hypothesis/following{/other_user}","gists_url":"https://api.github.com/users/hypothesis/gists{/gist_id}","starred_url":"https://api.github.com/users/hypothesis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hypothesis/subscriptions","organizations_url":"https://api.github.com/users/hypothesis/orgs","repos_url":"https://api.github.com/users/hypothesis/repos","events_url":"https://api.github.com/users/hypothesis/events{/privacy}","received_events_url":"https://api.github.com/users/hypothesis/received_events","type":"Organization","site_admin":false},"repo":{"id":3910945,"name":"h","full_name":"hypothesis/h","owner":{"login":"hypothesis","id":1243215,"avatar_url":"https://avatars.githubusercontent.com/u/1243215?v=3","gravatar_id":"","url":"https://api.github.com/users/hypothesis","html_url":"https://github.com/hypothesis","followers_url":"https://api.github.com/users/hypothesis/followers","following_url":"https://api.github.com/users/hypothesis/following{/other_user}","gists_url":"https://api.github.com/users/hypothesis/gists{/gist_id}","starred_url":"https://api.github.com/users/hypothesis/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/hypothesis/subscriptions","organizations_url":"https://api.github.com/users/hypothesis/orgs","repos_url":"https://api.github.com/users/hypothesis/repos","events_url":"https://api.github.com/users/hypothesis/events{/privacy}","received_events_url":"https://api.github.com/users/hypothesis/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/hypothesis/h","description":"The Internet, peer reviewed.","fork":false,"url":"https://api.github.com/repos/hypothesis/h","forks_url":"https://api.github.com/repos/hypothesis/h/forks","keys_url":"https://api.github.com/repos/hypothesis/h/keys{/key_id}","collaborators_url":"https://api.github.com/repos/hypothesis/h/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/hypothesis/h/teams","hooks_url":"https://api.github.com/repos/hypothesis/h/hooks","issue_events_url":"https://api.github.com/repos/hypothesis/h/issues/events{/number}","events_url":"https://api.github.com/repos/hypothesis/h/events","assignees_url":"https://api.github.com/repos/hypothesis/h/assignees{/user}","branches_url":"https://api.github.com/repos/hypothesis/h/branches{/branch}","tags_url":"https://api.github.com/repos/hypothesis/h/tags","blobs_url":"https://api.github.com/repos/hypothesis/h/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/hypothesis/h/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/hypothesis/h/git/refs{/sha}","trees_url":"https://api.github.com/repos/hypothesis/h/git/trees{/sha}","statuses_url":"https://api.github.com/repos/hypothesis/h/statuses/{sha}","languages_url":"https://api.github.com/repos/hypothesis/h/languages","stargazers_url":"https://api.github.com/repos/hypothesis/h/stargazers","contributors_url":"https://api.github.com/repos/hypothesis/h/contributors","subscribers_url":"https://api.github.com/repos/hypothesis/h/subscribers","subscription_url":"https://api.github.com/repos/hypothesis/h/subscription","commits_url":"https://api.github.com/repos/hypothesis/h/commits{/sha}","git_commits_url":"https://api.github.com/repos/hypothesis/h/git/commits{/sha}","comments_url":"https://api.github.com/repos/hypothesis/h/comments{/number}","issue_comment_url":"https://api.github.com/repos/hypothesis/h/issues/comments{/number}","contents_url":"https://api.github.com/repos/hypothesis/h/contents/{+path}","compare_url":"https://api.github.com/repos/hypothesis/h/compare/{base}...{head}","merges_url":"https://api.github.com/repos/hypothesis/h/merges","archive_url":"https://api.github.com/repos/hypothesis/h/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/hypothesis/h/downloads","issues_url":"https://api.github.com/repos/hypothesis/h/issues{/number}","pulls_url":"https://api.github.com/repos/hypothesis/h/pulls{/number}","milestones_url":"https://api.github.com/repos/hypothesis/h/milestones{/number}","notifications_url":"https://api.github.com/repos/hypothesis/h/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/hypothesis/h/labels{/name}","releases_url":"https://api.github.com/repos/hypothesis/h/releases{/id}","deployments_url":"https://api.github.com/repos/hypothesis/h/deployments","created_at":"2012-04-02T19:56:59Z","updated_at":"2016-05-08T02:27:46Z","pushed_at":"2016-05-11T10:37:40Z","git_url":"git://github.com/hypothesis/h.git","ssh_url":"git@github.com:hypothesis/h.git","clone_url":"https://github.com/hypothesis/h.git","svn_url":"https://github.com/hypothesis/h","homepage":"https://hypothes.is/","size":28457,"stargazers_count":632,"watchers_count":632,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":148,"mirror_url":null,"open_issues_count":70,"forks":148,"open_issues":70,"watchers":632,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/hypothesis/h/pulls/3309"},"html":{"href":"https://github.com/hypothesis/h/pull/3309"},"issue":{"href":"https://api.github.com/repos/hypothesis/h/issues/3309"},"comments":{"href":"https://api.github.com/repos/hypothesis/h/issues/3309/comments"},"review_comments":{"href":"https://api.github.com/repos/hypothesis/h/pulls/3309/comments"},"review_comment":{"href":"https://api.github.com/repos/hypothesis/h/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/hypothesis/h/pulls/3309/commits"},"statuses":{"href":"https://api.github.com/repos/hypothesis/h/statuses/1d715b1c215c6fbe1272fdbaeb8737bb5110c885"}}}}