We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Speed up tip migration script
Reticketed from https://github.com/gratipay/inside.gratipay.com/issues/468#issuecomment-171707621

When we wrote this script, we thought that we'd only be using it for a month or two. Now that it's here to stay - time to work on it a bit :) 

The script takes so long because we're pulling in _all_ approved teams. We could place a check in the script so that we only pull in those teams that satisfy the following criteria - 

1) Team owner must have 0+ tips from Gratipay 1.0
2) Team must have zero `payment_instructions` that have a `ctime` lesser than the team's `ctime` (i.e. migrated tips). We use the `ctime` attribute to differentiate `payment_instructions` that were created as a result of migrated tips vs created in Gratipay 2.0

https://github.com/gratipay/gratipay.com/blob/master/bin/migrate-tips.py#L6-L10


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `gratipay/models/team.py`
Content:
```
1 """Teams on Gratipay receive payments and distribute payroll.
2 """
3 import requests
4 from aspen import json, log
5 from gratipay.models import add_event
6 from postgres.orm import Model
7 
8 
9 class Team(Model):
10     """Represent a Gratipay team.
11     """
12 
13     typname = 'teams'
14 
15     def __eq__(self, other):
16         if not isinstance(other, Team):
17             return False
18         return self.id == other.id
19 
20     def __ne__(self, other):
21         if not isinstance(other, Team):
22             return True
23         return self.id != other.id
24 
25 
26     # Constructors
27     # ============
28 
29     @classmethod
30     def from_id(cls, id):
31         """Return an existing team based on id.
32         """
33         return cls._from_thing("id", id)
34 
35     @classmethod
36     def from_slug(cls, slug):
37         """Return an existing team based on slug.
38         """
39         return cls._from_thing("slug_lower", slug.lower())
40 
41     @classmethod
42     def _from_thing(cls, thing, value):
43         assert thing in ("id", "slug_lower")
44         return cls.db.one("""
45 
46             SELECT teams.*::teams
47               FROM teams
48              WHERE {}=%s
49 
50         """.format(thing), (value,))
51 
52     @classmethod
53     def insert(cls, owner, **fields):
54         fields['slug_lower'] = fields['slug'].lower()
55         fields['owner'] = owner.username
56         return cls.db.one("""
57 
58             INSERT INTO teams
59                         (slug, slug_lower, name, homepage,
60                          product_or_service, todo_url, onboarding_url,
61                          owner)
62                  VALUES (%(slug)s, %(slug_lower)s, %(name)s, %(homepage)s,
63                          %(product_or_service)s, %(todo_url)s, %(onboarding_url)s,
64                          %(owner)s)
65               RETURNING teams.*::teams
66 
67         """, fields)
68 
69 
70     def create_github_review_issue(self):
71         """POST to GitHub, and return the URL of the new issue.
72         """
73         api_url = "https://api.github.com/repos/{}/issues".format(self.review_repo)
74         data = json.dumps({ "title": self.name
75                           , "body": "https://gratipay.com/{}/\n\n".format(self.slug) +
76                                     "(This application will remain open for at least a week.)"
77                            })
78         out = ''
79         try:
80             r = requests.post(api_url, auth=self.review_auth, data=data)
81             if r.status_code == 201:
82                 out = r.json()['html_url']
83             else:
84                 log(r.status_code)
85                 log(r.text)
86             err = str(r.status_code)
87         except:
88             err = "eep"
89         if not out:
90             out = "https://github.com/gratipay/team-review/issues#error-{}".format(err)
91         return out
92 
93 
94     def set_review_url(self, review_url):
95         self.db.run("UPDATE teams SET review_url=%s WHERE id=%s", (review_url, self.id))
96         self.set_attributes(review_url=review_url)
97 
98 
99     def get_og_title(self):
100         out = self.name
101         receiving = self.receiving
102         if receiving > 0:
103             out += " receives $%.2f/wk" % receiving
104         else:
105             out += " is"
106         return out + " on Gratipay"
107 
108 
109     def update_receiving(self, cursor=None):
110         r = (cursor or self.db).one("""
111             WITH our_receiving AS (
112                      SELECT amount
113                        FROM current_payment_instructions
114                        JOIN participants p ON p.username = participant
115                       WHERE team = %(slug)s
116                         AND p.is_suspicious IS NOT true
117                         AND amount > 0
118                         AND is_funded
119                  )
120             UPDATE teams t
121                SET receiving = COALESCE((SELECT sum(amount) FROM our_receiving), 0)
122                  , nreceiving_from = COALESCE((SELECT count(*) FROM our_receiving), 0)
123                  , distributing = COALESCE((SELECT sum(amount) FROM our_receiving), 0)
124                  , ndistributing_to = 1
125              WHERE t.slug = %(slug)s
126          RETURNING receiving, nreceiving_from, distributing, ndistributing_to
127         """, dict(slug=self.slug))
128 
129 
130         # This next step is easy for now since we don't have payroll.
131         from gratipay.models.participant import Participant
132         Participant.from_username(self.owner).update_taking(cursor or self.db)
133 
134         self.set_attributes( receiving=r.receiving
135                            , nreceiving_from=r.nreceiving_from
136                            , distributing=r.distributing
137                            , ndistributing_to=r.ndistributing_to
138                             )
139 
140     @property
141     def status(self):
142         return { None: 'unreviewed'
143                , False: 'rejected'
144                , True: 'approved'
145                 }[self.is_approved]
146 
147     def to_dict(self):
148         return {
149             'homepage': self.homepage,
150             'name': self.name,
151             'nreceiving_from': self.nreceiving_from,
152             'onboarding_url': self.onboarding_url,
153             'owner': '~' + self.owner,
154             'receiving': self.receiving,
155             'slug': self.slug,
156             'status': self.status,
157             'todo_url': self.todo_url
158         }
159 
160     def migrate_tips(self):
161         payment_instructions = self.db.all("""
162             SELECT pi.*
163               FROM payment_instructions pi
164               JOIN teams t ON t.slug = pi.team
165               JOIN participants p ON t.owner = p.username
166              WHERE p.username = %s
167                AND pi.ctime < t.ctime
168         """, (self.owner, ))
169 
170         # Make sure the migration hasn't been done already
171         if payment_instructions:
172             raise AlreadyMigrated
173 
174         return self.db.one("""
175         WITH rows AS (
176 
177             INSERT INTO payment_instructions
178                         (ctime, mtime, participant, team, amount, is_funded)
179                  SELECT ct.ctime
180                       , ct.mtime
181                       , ct.tipper
182                       , %(slug)s
183                       , ct.amount
184                       , ct.is_funded
185                    FROM current_tips ct
186                    JOIN participants p ON p.username = tipper
187                   WHERE ct.tippee=%(owner)s
188                     AND p.claimed_time IS NOT NULL
189                     AND p.is_suspicious IS NOT TRUE
190                     AND p.is_closed IS NOT TRUE
191               RETURNING 1
192 
193         ) SELECT count(*) FROM rows;
194         """, {'slug': self.slug, 'owner': self.owner})
195 
196 
197     # Images
198     # ======
199 
200     IMAGE_SIZES = ('original', 'large', 'small')
201 
202     def get_image_url(self, size):
203         assert size in ('original', 'large', 'small'), size
204         return '/{}/image?size={}'.format(self.slug, size)
205 
206     def save_image(self, original, large, small, image_type):
207         with self.db.get_cursor() as c:
208             oids = {}
209             for size in self.IMAGE_SIZES:
210                 lobject = c.connection.lobject(getattr(self, 'image_oid_'+size), mode='wb')
211                 lobject.write(locals()[size])
212                 oids[size] = lobject.oid
213                 lobject.close()
214 
215             c.run("""UPDATE teams
216                         SET image_oid_original=%s, image_oid_large=%s, image_oid_small=%s
217                           , image_type=%s
218                       WHERE id=%s"""
219                  , (oids['original'], oids['large'], oids['small'], image_type, self.id)
220                   )
221             add_event(c, 'team', dict( action='upsert_image'
222                                      , id=self.id
223                                      , **oids
224                                       ))
225             self.set_attributes( image_type=image_type
226                                , **{'image_oid_'+size: oids[size] for size in oids}
227                                 )
228             return oids
229 
230     def load_image(self, size):
231         assert size in self.IMAGE_SIZES, size
232         image = None
233         oid = getattr(self, 'image_oid_{}'.format(size))
234         if oid != 0:
235             with self.db.get_connection() as c:
236                 image = c.lobject(oid, mode='rb').read()
237         return image
238 
239 
240 class AlreadyMigrated(Exception): pass
241
```

Path: `bin/migrate-tips.py`
Content:
```
1 from gratipay.wireup import db, env
2 from gratipay.models.team import Team, AlreadyMigrated
3 
4 db = db(env())
5 
6 slugs = db.all("""
7     SELECT slug
8       FROM teams
9      WHERE is_approved IS TRUE
10 """)
11 
12 for slug in slugs:
13     team = Team.from_slug(slug)
14     try:
15         ntips = team.migrate_tips()
16         print("Migrated {} tip(s) for '{}'".format(ntips, slug))
17     except AlreadyMigrated:
18         print("'%s' already migrated." % slug)
19 
20 print("Done.")
21
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/bin/migrate-tips.py b/bin/migrate-tips.py
--- a/bin/migrate-tips.py
+++ b/bin/migrate-tips.py
@@ -1,20 +1,27 @@
 from gratipay.wireup import db, env
-from gratipay.models.team import Team, AlreadyMigrated
+from gratipay.models.team import AlreadyMigrated
 
 db = db(env())
 
-slugs = db.all("""
-    SELECT slug
-      FROM teams
-     WHERE is_approved IS TRUE
+teams = db.all("""
+    SELECT distinct ON (t.slug) t.*::teams
+      FROM teams t
+      JOIN tips ON t.owner = tips.tippee    -- Only fetch teams whose owners had tips under Gratipay 1.0
+     WHERE t.is_approved IS TRUE            -- Only fetch approved teams
+       AND NOT EXISTS (                     -- Make sure tips haven't been migrated for any teams with same owner
+            SELECT 1
+              FROM payment_instructions pi
+              JOIN teams t2 ON t2.slug = pi.team
+             WHERE t2.owner = t.owner
+               AND pi.ctime < t2.ctime
+       )
 """)
 
-for slug in slugs:
-    team = Team.from_slug(slug)
+for team in teams:
     try:
         ntips = team.migrate_tips()
-        print("Migrated {} tip(s) for '{}'".format(ntips, slug))
+        print("Migrated {} tip(s) for '{}'".format(ntips, team.slug))
     except AlreadyMigrated:
-        print("'%s' already migrated." % slug)
+        print("'%s' already migrated." % team.slug)
 
 print("Done.")
diff --git a/gratipay/models/team.py b/gratipay/models/team.py
--- a/gratipay/models/team.py
+++ b/gratipay/models/team.py
@@ -162,8 +162,7 @@
             SELECT pi.*
               FROM payment_instructions pi
               JOIN teams t ON t.slug = pi.team
-              JOIN participants p ON t.owner = p.username
-             WHERE p.username = %s
+             WHERE t.owner = %s
                AND pi.ctime < t.ctime
         """, (self.owner, ))

{"golden_diff": "diff --git a/bin/migrate-tips.py b/bin/migrate-tips.py\n--- a/bin/migrate-tips.py\n+++ b/bin/migrate-tips.py\n@@ -1,20 +1,27 @@\n from gratipay.wireup import db, env\n-from gratipay.models.team import Team, AlreadyMigrated\n+from gratipay.models.team import AlreadyMigrated\n \n db = db(env())\n \n-slugs = db.all(\"\"\"\n-    SELECT slug\n-      FROM teams\n-     WHERE is_approved IS TRUE\n+teams = db.all(\"\"\"\n+    SELECT distinct ON (t.slug) t.*::teams\n+      FROM teams t\n+      JOIN tips ON t.owner = tips.tippee    -- Only fetch teams whose owners had tips under Gratipay 1.0\n+     WHERE t.is_approved IS TRUE            -- Only fetch approved teams\n+       AND NOT EXISTS (                     -- Make sure tips haven't been migrated for any teams with same owner\n+            SELECT 1\n+              FROM payment_instructions pi\n+              JOIN teams t2 ON t2.slug = pi.team\n+             WHERE t2.owner = t.owner\n+               AND pi.ctime < t2.ctime\n+       )\n \"\"\")\n \n-for slug in slugs:\n-    team = Team.from_slug(slug)\n+for team in teams:\n     try:\n         ntips = team.migrate_tips()\n-        print(\"Migrated {} tip(s) for '{}'\".format(ntips, slug))\n+        print(\"Migrated {} tip(s) for '{}'\".format(ntips, team.slug))\n     except AlreadyMigrated:\n-        print(\"'%s' already migrated.\" % slug)\n+        print(\"'%s' already migrated.\" % team.slug)\n \n print(\"Done.\")\ndiff --git a/gratipay/models/team.py b/gratipay/models/team.py\n--- a/gratipay/models/team.py\n+++ b/gratipay/models/team.py\n@@ -162,8 +162,7 @@\n             SELECT pi.*\n               FROM payment_instructions pi\n               JOIN teams t ON t.slug = pi.team\n-              JOIN participants p ON t.owner = p.username\n-             WHERE p.username = %s\n+             WHERE t.owner = %s\n                AND pi.ctime < t.ctime\n         \"\"\", (self.owner, ))\n", "issue": "Speed up tip migration script\nReticketed from https://github.com/gratipay/inside.gratipay.com/issues/468#issuecomment-171707621\n\nWhen we wrote this script, we thought that we'd only be using it for a month or two. Now that it's here to stay - time to work on it a bit :) \n\nThe script takes so long because we're pulling in _all_ approved teams. We could place a check in the script so that we only pull in those teams that satisfy the following criteria - \n\n1) Team owner must have 0+ tips from Gratipay 1.0\n2) Team must have zero `payment_instructions` that have a `ctime` lesser than the team's `ctime` (i.e. migrated tips). We use the `ctime` attribute to differentiate `payment_instructions` that were created as a result of migrated tips vs created in Gratipay 2.0\n\nhttps://github.com/gratipay/gratipay.com/blob/master/bin/migrate-tips.py#L6-L10\n\n", "before_files": [{"content": "\"\"\"Teams on Gratipay receive payments and distribute payroll.\n\"\"\"\nimport requests\nfrom aspen import json, log\nfrom gratipay.models import add_event\nfrom postgres.orm import Model\n\n\nclass Team(Model):\n    \"\"\"Represent a Gratipay team.\n    \"\"\"\n\n    typname = 'teams'\n\n    def __eq__(self, other):\n        if not isinstance(other, Team):\n            return False\n        return self.id == other.id\n\n    def __ne__(self, other):\n        if not isinstance(other, Team):\n            return True\n        return self.id != other.id\n\n\n    # Constructors\n    # ============\n\n    @classmethod\n    def from_id(cls, id):\n        \"\"\"Return an existing team based on id.\n        \"\"\"\n        return cls._from_thing(\"id\", id)\n\n    @classmethod\n    def from_slug(cls, slug):\n        \"\"\"Return an existing team based on slug.\n        \"\"\"\n        return cls._from_thing(\"slug_lower\", slug.lower())\n\n    @classmethod\n    def _from_thing(cls, thing, value):\n        assert thing in (\"id\", \"slug_lower\")\n        return cls.db.one(\"\"\"\n\n            SELECT teams.*::teams\n              FROM teams\n             WHERE {}=%s\n\n        \"\"\".format(thing), (value,))\n\n    @classmethod\n    def insert(cls, owner, **fields):\n        fields['slug_lower'] = fields['slug'].lower()\n        fields['owner'] = owner.username\n        return cls.db.one(\"\"\"\n\n            INSERT INTO teams\n                        (slug, slug_lower, name, homepage,\n                         product_or_service, todo_url, onboarding_url,\n                         owner)\n                 VALUES (%(slug)s, %(slug_lower)s, %(name)s, %(homepage)s,\n                         %(product_or_service)s, %(todo_url)s, %(onboarding_url)s,\n                         %(owner)s)\n              RETURNING teams.*::teams\n\n        \"\"\", fields)\n\n\n    def create_github_review_issue(self):\n        \"\"\"POST to GitHub, and return the URL of the new issue.\n        \"\"\"\n        api_url = \"https://api.github.com/repos/{}/issues\".format(self.review_repo)\n        data = json.dumps({ \"title\": self.name\n                          , \"body\": \"https://gratipay.com/{}/\\n\\n\".format(self.slug) +\n                                    \"(This application will remain open for at least a week.)\"\n                           })\n        out = ''\n        try:\n            r = requests.post(api_url, auth=self.review_auth, data=data)\n            if r.status_code == 201:\n                out = r.json()['html_url']\n            else:\n                log(r.status_code)\n                log(r.text)\n            err = str(r.status_code)\n        except:\n            err = \"eep\"\n        if not out:\n            out = \"https://github.com/gratipay/team-review/issues#error-{}\".format(err)\n        return out\n\n\n    def set_review_url(self, review_url):\n        self.db.run(\"UPDATE teams SET review_url=%s WHERE id=%s\", (review_url, self.id))\n        self.set_attributes(review_url=review_url)\n\n\n    def get_og_title(self):\n        out = self.name\n        receiving = self.receiving\n        if receiving > 0:\n            out += \" receives $%.2f/wk\" % receiving\n        else:\n            out += \" is\"\n        return out + \" on Gratipay\"\n\n\n    def update_receiving(self, cursor=None):\n        r = (cursor or self.db).one(\"\"\"\n            WITH our_receiving AS (\n                     SELECT amount\n                       FROM current_payment_instructions\n                       JOIN participants p ON p.username = participant\n                      WHERE team = %(slug)s\n                        AND p.is_suspicious IS NOT true\n                        AND amount > 0\n                        AND is_funded\n                 )\n            UPDATE teams t\n               SET receiving = COALESCE((SELECT sum(amount) FROM our_receiving), 0)\n                 , nreceiving_from = COALESCE((SELECT count(*) FROM our_receiving), 0)\n                 , distributing = COALESCE((SELECT sum(amount) FROM our_receiving), 0)\n                 , ndistributing_to = 1\n             WHERE t.slug = %(slug)s\n         RETURNING receiving, nreceiving_from, distributing, ndistributing_to\n        \"\"\", dict(slug=self.slug))\n\n\n        # This next step is easy for now since we don't have payroll.\n        from gratipay.models.participant import Participant\n        Participant.from_username(self.owner).update_taking(cursor or self.db)\n\n        self.set_attributes( receiving=r.receiving\n                           , nreceiving_from=r.nreceiving_from\n                           , distributing=r.distributing\n                           , ndistributing_to=r.ndistributing_to\n                            )\n\n    @property\n    def status(self):\n        return { None: 'unreviewed'\n               , False: 'rejected'\n               , True: 'approved'\n                }[self.is_approved]\n\n    def to_dict(self):\n        return {\n            'homepage': self.homepage,\n            'name': self.name,\n            'nreceiving_from': self.nreceiving_from,\n            'onboarding_url': self.onboarding_url,\n            'owner': '~' + self.owner,\n            'receiving': self.receiving,\n            'slug': self.slug,\n            'status': self.status,\n            'todo_url': self.todo_url\n        }\n\n    def migrate_tips(self):\n        payment_instructions = self.db.all(\"\"\"\n            SELECT pi.*\n              FROM payment_instructions pi\n              JOIN teams t ON t.slug = pi.team\n              JOIN participants p ON t.owner = p.username\n             WHERE p.username = %s\n               AND pi.ctime < t.ctime\n        \"\"\", (self.owner, ))\n\n        # Make sure the migration hasn't been done already\n        if payment_instructions:\n            raise AlreadyMigrated\n\n        return self.db.one(\"\"\"\n        WITH rows AS (\n\n            INSERT INTO payment_instructions\n                        (ctime, mtime, participant, team, amount, is_funded)\n                 SELECT ct.ctime\n                      , ct.mtime\n                      , ct.tipper\n                      , %(slug)s\n                      , ct.amount\n                      , ct.is_funded\n                   FROM current_tips ct\n                   JOIN participants p ON p.username = tipper\n                  WHERE ct.tippee=%(owner)s\n                    AND p.claimed_time IS NOT NULL\n                    AND p.is_suspicious IS NOT TRUE\n                    AND p.is_closed IS NOT TRUE\n              RETURNING 1\n\n        ) SELECT count(*) FROM rows;\n        \"\"\", {'slug': self.slug, 'owner': self.owner})\n\n\n    # Images\n    # ======\n\n    IMAGE_SIZES = ('original', 'large', 'small')\n\n    def get_image_url(self, size):\n        assert size in ('original', 'large', 'small'), size\n        return '/{}/image?size={}'.format(self.slug, size)\n\n    def save_image(self, original, large, small, image_type):\n        with self.db.get_cursor() as c:\n            oids = {}\n            for size in self.IMAGE_SIZES:\n                lobject = c.connection.lobject(getattr(self, 'image_oid_'+size), mode='wb')\n                lobject.write(locals()[size])\n                oids[size] = lobject.oid\n                lobject.close()\n\n            c.run(\"\"\"UPDATE teams\n                        SET image_oid_original=%s, image_oid_large=%s, image_oid_small=%s\n                          , image_type=%s\n                      WHERE id=%s\"\"\"\n                 , (oids['original'], oids['large'], oids['small'], image_type, self.id)\n                  )\n            add_event(c, 'team', dict( action='upsert_image'\n                                     , id=self.id\n                                     , **oids\n                                      ))\n            self.set_attributes( image_type=image_type\n                               , **{'image_oid_'+size: oids[size] for size in oids}\n                                )\n            return oids\n\n    def load_image(self, size):\n        assert size in self.IMAGE_SIZES, size\n        image = None\n        oid = getattr(self, 'image_oid_{}'.format(size))\n        if oid != 0:\n            with self.db.get_connection() as c:\n                image = c.lobject(oid, mode='rb').read()\n        return image\n\n\nclass AlreadyMigrated(Exception): pass\n", "path": "gratipay/models/team.py"}, {"content": "from gratipay.wireup import db, env\nfrom gratipay.models.team import Team, AlreadyMigrated\n\ndb = db(env())\n\nslugs = db.all(\"\"\"\n    SELECT slug\n      FROM teams\n     WHERE is_approved IS TRUE\n\"\"\")\n\nfor slug in slugs:\n    team = Team.from_slug(slug)\n    try:\n        ntips = team.migrate_tips()\n        print(\"Migrated {} tip(s) for '{}'\".format(ntips, slug))\n    except AlreadyMigrated:\n        print(\"'%s' already migrated.\" % slug)\n\nprint(\"Done.\")\n", "path": "bin/migrate-tips.py"}], "after_files": [{"content": "\"\"\"Teams on Gratipay receive payments and distribute payroll.\n\"\"\"\nimport requests\nfrom aspen import json, log\nfrom gratipay.models import add_event\nfrom postgres.orm import Model\n\n\nclass Team(Model):\n    \"\"\"Represent a Gratipay team.\n    \"\"\"\n\n    typname = 'teams'\n\n    def __eq__(self, other):\n        if not isinstance(other, Team):\n            return False\n        return self.id == other.id\n\n    def __ne__(self, other):\n        if not isinstance(other, Team):\n            return True\n        return self.id != other.id\n\n\n    # Constructors\n    # ============\n\n    @classmethod\n    def from_id(cls, id):\n        \"\"\"Return an existing team based on id.\n        \"\"\"\n        return cls._from_thing(\"id\", id)\n\n    @classmethod\n    def from_slug(cls, slug):\n        \"\"\"Return an existing team based on slug.\n        \"\"\"\n        return cls._from_thing(\"slug_lower\", slug.lower())\n\n    @classmethod\n    def _from_thing(cls, thing, value):\n        assert thing in (\"id\", \"slug_lower\")\n        return cls.db.one(\"\"\"\n\n            SELECT teams.*::teams\n              FROM teams\n             WHERE {}=%s\n\n        \"\"\".format(thing), (value,))\n\n    @classmethod\n    def insert(cls, owner, **fields):\n        fields['slug_lower'] = fields['slug'].lower()\n        fields['owner'] = owner.username\n        return cls.db.one(\"\"\"\n\n            INSERT INTO teams\n                        (slug, slug_lower, name, homepage,\n                         product_or_service, todo_url, onboarding_url,\n                         owner)\n                 VALUES (%(slug)s, %(slug_lower)s, %(name)s, %(homepage)s,\n                         %(product_or_service)s, %(todo_url)s, %(onboarding_url)s,\n                         %(owner)s)\n              RETURNING teams.*::teams\n\n        \"\"\", fields)\n\n\n    def create_github_review_issue(self):\n        \"\"\"POST to GitHub, and return the URL of the new issue.\n        \"\"\"\n        api_url = \"https://api.github.com/repos/{}/issues\".format(self.review_repo)\n        data = json.dumps({ \"title\": self.name\n                          , \"body\": \"https://gratipay.com/{}/\\n\\n\".format(self.slug) +\n                                    \"(This application will remain open for at least a week.)\"\n                           })\n        out = ''\n        try:\n            r = requests.post(api_url, auth=self.review_auth, data=data)\n            if r.status_code == 201:\n                out = r.json()['html_url']\n            else:\n                log(r.status_code)\n                log(r.text)\n            err = str(r.status_code)\n        except:\n            err = \"eep\"\n        if not out:\n            out = \"https://github.com/gratipay/team-review/issues#error-{}\".format(err)\n        return out\n\n\n    def set_review_url(self, review_url):\n        self.db.run(\"UPDATE teams SET review_url=%s WHERE id=%s\", (review_url, self.id))\n        self.set_attributes(review_url=review_url)\n\n\n    def get_og_title(self):\n        out = self.name\n        receiving = self.receiving\n        if receiving > 0:\n            out += \" receives $%.2f/wk\" % receiving\n        else:\n            out += \" is\"\n        return out + \" on Gratipay\"\n\n\n    def update_receiving(self, cursor=None):\n        r = (cursor or self.db).one(\"\"\"\n            WITH our_receiving AS (\n                     SELECT amount\n                       FROM current_payment_instructions\n                       JOIN participants p ON p.username = participant\n                      WHERE team = %(slug)s\n                        AND p.is_suspicious IS NOT true\n                        AND amount > 0\n                        AND is_funded\n                 )\n            UPDATE teams t\n               SET receiving = COALESCE((SELECT sum(amount) FROM our_receiving), 0)\n                 , nreceiving_from = COALESCE((SELECT count(*) FROM our_receiving), 0)\n                 , distributing = COALESCE((SELECT sum(amount) FROM our_receiving), 0)\n                 , ndistributing_to = 1\n             WHERE t.slug = %(slug)s\n         RETURNING receiving, nreceiving_from, distributing, ndistributing_to\n        \"\"\", dict(slug=self.slug))\n\n\n        # This next step is easy for now since we don't have payroll.\n        from gratipay.models.participant import Participant\n        Participant.from_username(self.owner).update_taking(cursor or self.db)\n\n        self.set_attributes( receiving=r.receiving\n                           , nreceiving_from=r.nreceiving_from\n                           , distributing=r.distributing\n                           , ndistributing_to=r.ndistributing_to\n                            )\n\n    @property\n    def status(self):\n        return { None: 'unreviewed'\n               , False: 'rejected'\n               , True: 'approved'\n                }[self.is_approved]\n\n    def to_dict(self):\n        return {\n            'homepage': self.homepage,\n            'name': self.name,\n            'nreceiving_from': self.nreceiving_from,\n            'onboarding_url': self.onboarding_url,\n            'owner': '~' + self.owner,\n            'receiving': self.receiving,\n            'slug': self.slug,\n            'status': self.status,\n            'todo_url': self.todo_url\n        }\n\n    def migrate_tips(self):\n        payment_instructions = self.db.all(\"\"\"\n            SELECT pi.*\n              FROM payment_instructions pi\n              JOIN teams t ON t.slug = pi.team\n             WHERE t.owner = %s\n               AND pi.ctime < t.ctime\n        \"\"\", (self.owner, ))\n\n        # Make sure the migration hasn't been done already\n        if payment_instructions:\n            raise AlreadyMigrated\n\n        return self.db.one(\"\"\"\n        WITH rows AS (\n\n            INSERT INTO payment_instructions\n                        (ctime, mtime, participant, team, amount, is_funded)\n                 SELECT ct.ctime\n                      , ct.mtime\n                      , ct.tipper\n                      , %(slug)s\n                      , ct.amount\n                      , ct.is_funded\n                   FROM current_tips ct\n                   JOIN participants p ON p.username = tipper\n                  WHERE ct.tippee=%(owner)s\n                    AND p.claimed_time IS NOT NULL\n                    AND p.is_suspicious IS NOT TRUE\n                    AND p.is_closed IS NOT TRUE\n              RETURNING 1\n\n        ) SELECT count(*) FROM rows;\n        \"\"\", {'slug': self.slug, 'owner': self.owner})\n\n\n    # Images\n    # ======\n\n    IMAGE_SIZES = ('original', 'large', 'small')\n\n    def get_image_url(self, size):\n        assert size in ('original', 'large', 'small'), size\n        return '/{}/image?size={}'.format(self.slug, size)\n\n    def save_image(self, original, large, small, image_type):\n        with self.db.get_cursor() as c:\n            oids = {}\n            for size in self.IMAGE_SIZES:\n                lobject = c.connection.lobject(getattr(self, 'image_oid_'+size), mode='wb')\n                lobject.write(locals()[size])\n                oids[size] = lobject.oid\n                lobject.close()\n\n            c.run(\"\"\"UPDATE teams\n                        SET image_oid_original=%s, image_oid_large=%s, image_oid_small=%s\n                          , image_type=%s\n                      WHERE id=%s\"\"\"\n                 , (oids['original'], oids['large'], oids['small'], image_type, self.id)\n                  )\n            add_event(c, 'team', dict( action='upsert_image'\n                                     , id=self.id\n                                     , **oids\n                                      ))\n            self.set_attributes( image_type=image_type\n                               , **{'image_oid_'+size: oids[size] for size in oids}\n                                )\n            return oids\n\n    def load_image(self, size):\n        assert size in self.IMAGE_SIZES, size\n        image = None\n        oid = getattr(self, 'image_oid_{}'.format(size))\n        if oid != 0:\n            with self.db.get_connection() as c:\n                image = c.lobject(oid, mode='rb').read()\n        return image\n\n\nclass AlreadyMigrated(Exception): pass\n", "path": "gratipay/models/team.py"}, {"content": "from gratipay.wireup import db, env\nfrom gratipay.models.team import AlreadyMigrated\n\ndb = db(env())\n\nteams = db.all(\"\"\"\n    SELECT distinct ON (t.slug) t.*::teams\n      FROM teams t\n      JOIN tips ON t.owner = tips.tippee    -- Only fetch teams whose owners had tips under Gratipay 1.0\n     WHERE t.is_approved IS TRUE            -- Only fetch approved teams\n       AND NOT EXISTS (                     -- Make sure tips haven't been migrated for any teams with same owner\n            SELECT 1\n              FROM payment_instructions pi\n              JOIN teams t2 ON t2.slug = pi.team\n             WHERE t2.owner = t.owner\n               AND pi.ctime < t2.ctime\n       )\n\"\"\")\n\nfor team in teams:\n    try:\n        ntips = team.migrate_tips()\n        print(\"Migrated {} tip(s) for '{}'\".format(ntips, team.slug))\n    except AlreadyMigrated:\n        print(\"'%s' already migrated.\" % team.slug)\n\nprint(\"Done.\")\n", "path": "bin/migrate-tips.py"}]}