We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Currency exchange rate between EUR and USD are mixed up
If I give euros to an account using dollar as main currency, the currency rate gets mixed up somewhere.
For instance if I give 5€, on the creator's page it only displays 4.05$ instead of 6.18$.

Current exchange rate:
* 5 EUR to USD: 6.18$
* 5 USD to EUR: 4.05€

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `liberapay/models/_mixin_team.py`
Content:
```
1 """Teams are groups of participants.
2 """
3 from __future__ import division, print_function, unicode_literals
4 
5 from collections import OrderedDict
6 from statistics import median
7 
8 from mangopay.utils import Money
9 
10 from liberapay.constants import ZERO, TAKE_THROTTLING_THRESHOLD
11 from liberapay.utils import NS
12 
13 
14 class MemberLimitReached(Exception): pass
15 
16 
17 class InactiveParticipantAdded(Exception): pass
18 
19 
20 class MixinTeam(object):
21 
22     def invite(self, invitee, inviter):
23         assert self.kind == 'group'
24         with self.db.get_cursor() as c:
25             n_id = invitee.notify(
26                 'team_invite',
27                 team=self.username,
28                 team_url=self.url(),
29                 inviter=inviter.username,
30             )
31             payload = dict(invitee=invitee.id, notification_id=n_id)
32             self.add_event(c, 'invite', payload, inviter.id)
33 
34     def add_member(self, member, cursor=None):
35         """Add a member to this team.
36         """
37         if self.nmembers >= 149:
38             raise MemberLimitReached
39         if member.status != 'active':
40             raise InactiveParticipantAdded
41         self.set_take_for(member, ZERO[self.main_currency], self, cursor=cursor)
42 
43     def remove_all_members(self, cursor=None):
44         (cursor or self.db).run("""
45             INSERT INTO takes
46                         (ctime, member, team, amount, actual_amount, recorder)
47                  SELECT ctime, member, %(id)s, NULL, NULL, %(id)s
48                    FROM current_takes
49                   WHERE team=%(id)s
50         """, dict(id=self.id))
51 
52     def member_of(self, team):
53         """Given a Participant object, return a boolean.
54         """
55         assert team.kind == 'group'
56         return self.db.one("""
57             SELECT true
58               FROM current_takes
59              WHERE team=%s AND member=%s
60         """, (team.id, self.id), default=False)
61 
62     def get_takes_last_week(self):
63         """Get the users' nominal takes last week. Used in throttling.
64         """
65         assert self.kind == 'group'
66         takes = {t.member: t.amount for t in self.db.all("""
67 
68             SELECT DISTINCT ON (member) member, amount, mtime
69               FROM takes
70              WHERE team=%s
71                AND mtime < (
72                        SELECT ts_start
73                          FROM paydays
74                         WHERE ts_end > ts_start
75                      ORDER BY ts_start DESC LIMIT 1
76                    )
77           ORDER BY member, mtime DESC
78 
79         """, (self.id,)) if t.amount}
80         return takes
81 
82     def get_take_for(self, member):
83         """Return the nominal take for this member, or None.
84         """
85         return self.db.one(
86             "SELECT amount FROM current_takes WHERE member = %s AND team = %s",
87             (member.id, self.id)
88         )
89 
90     def compute_max_this_week(self, member_id, last_week):
91         """2x the member's take last week, or the member's take last week + the
92         leftover, or last week's median take, or 1.00, or infinity if the takes
93         were all zero last week or if throttling is disabled.
94         """
95         if not self.throttle_takes:
96             return
97         sum_last_week = sum(last_week.values())
98         if sum_last_week == 0:
99             return
100         sum_last_week = sum_last_week.convert(self.main_currency)
101         initial_leftover = self.receiving - sum_last_week
102         nonzero_last_week = [a.convert(self.main_currency).amount for a in last_week.values() if a]
103         member_last_week = last_week.get(member_id, ZERO[self.main_currency]).convert(self.main_currency)
104         return max(
105             member_last_week * 2,
106             member_last_week + initial_leftover,
107             Money(median(nonzero_last_week or (0,)), self.main_currency),
108             TAKE_THROTTLING_THRESHOLD[self.main_currency]
109         )
110 
111     def set_take_for(self, member, take, recorder, check_max=True, cursor=None):
112         """Sets member's take from the team pool.
113         """
114         assert self.kind == 'group'
115 
116         if recorder.id != self.id:
117             cur_take = self.get_take_for(member)
118             if cur_take is None:
119                 return None
120 
121         if not isinstance(take, (None.__class__, Money)):
122             take = Money(take, self.main_currency)  # TODO drop this
123 
124         with self.db.get_cursor(cursor) as cursor:
125             # Lock to avoid race conditions
126             cursor.run("LOCK TABLE takes IN EXCLUSIVE MODE")
127             # Throttle the new take, if there is more than one member
128             threshold = TAKE_THROTTLING_THRESHOLD[self.main_currency]
129             if take and check_max and self.nmembers > 1 and take > threshold:
130                 last_week = self.get_takes_last_week()
131                 max_this_week = self.compute_max_this_week(member.id, last_week)
132                 if max_this_week is not None and take > max_this_week:
133                     take = max_this_week
134             # Insert the new take
135             cursor.run("""
136 
137                 INSERT INTO takes
138                             (ctime, member, team, amount, actual_amount, recorder)
139                      SELECT COALESCE((
140                                 SELECT ctime
141                                   FROM takes
142                                  WHERE member=%(member)s
143                                    AND team=%(team)s
144                                  LIMIT 1
145                             ), current_timestamp)
146                           , %(member)s
147                           , %(team)s
148                           , %(amount)s
149                           , CASE WHEN %(amount)s IS NULL THEN NULL ELSE
150                                 COALESCE((
151                                     SELECT actual_amount
152                                       FROM takes
153                                      WHERE member=%(member)s
154                                        AND team=%(team)s
155                                   ORDER BY mtime DESC
156                                      LIMIT 1
157                                 ), zero(%(amount)s::currency_amount))
158                             END
159                           , %(recorder)s
160 
161             """, dict(member=member.id, team=self.id, amount=take,
162                       recorder=recorder.id))
163             # Recompute the actual takes and update the cached amounts
164             self.recompute_actual_takes(cursor, member=member)
165             # Update is_funded on member's tips
166             member.update_giving(cursor)
167 
168         return take
169 
170     def get_current_takes(self, cursor=None):
171         """Return a list of member takes for a team.
172         """
173         assert self.kind == 'group'
174         TAKES = """
175             SELECT p.id AS member_id, p.username AS member_name, p.avatar_url
176                  , (p.mangopay_user_id IS NOT NULL) AS is_identified, p.is_suspended
177                  , t.amount, t.actual_amount, t.ctime, t.mtime
178               FROM current_takes t
179               JOIN participants p ON p.id = member
180              WHERE t.team=%(team)s
181           ORDER BY p.username
182         """
183         records = (cursor or self.db).all(TAKES, dict(team=self.id))
184         return [r._asdict() for r in records]
185 
186     def recompute_actual_takes(self, cursor, member=None):
187         """Get the tips and takes for this team and recompute the actual amounts.
188 
189         To avoid deadlocks the given `cursor` should have already acquired an
190         exclusive lock on the `takes` table.
191         """
192         from liberapay.billing.payday import Payday
193         tips = [NS(t._asdict()) for t in cursor.all("""
194             SELECT t.id, t.tipper, t.amount AS full_amount
195                  , coalesce_currency_amount((
196                        SELECT sum(tr.amount, t.amount::currency)
197                          FROM transfers tr
198                         WHERE tr.tipper = t.tipper
199                           AND tr.team = %(team_id)s
200                           AND tr.context = 'take'
201                           AND tr.status = 'succeeded'
202                    ), t.amount::currency) AS past_transfers_sum
203               FROM current_tips t
204               JOIN participants p ON p.id = t.tipper
205              WHERE t.tippee = %(team_id)s
206                AND t.is_funded
207                AND p.is_suspended IS NOT true
208         """, dict(team_id=self.id))]
209         takes = [NS(r._asdict()) for r in (cursor or self.db).all("""
210             SELECT t.*
211               FROM current_takes t
212               JOIN participants p ON p.id = t.member
213              WHERE t.team = %s
214                AND p.is_suspended IS NOT true
215                AND p.mangopay_user_id IS NOT NULL
216         """, (self.id,))]
217         # Recompute the takes
218         takes_sum = {}
219         tippers = {}
220         transfers, new_leftover = Payday.resolve_takes(tips, takes, self.main_currency)
221         for t in transfers:
222             if t.member in takes_sum:
223                 takes_sum[t.member] += t.amount
224             else:
225                 takes_sum[t.member] = t.amount
226             if t.member in tippers:
227                 tippers[t.member].add(t.tipper)
228             else:
229                 tippers[t.member] = set((t.tipper,))
230         # Update the leftover
231         cursor.run("UPDATE participants SET leftover = %s WHERE id = %s",
232                    (new_leftover, self.id))
233         self.set_attributes(leftover=new_leftover)
234         # Update the cached amounts (actual_amount, taking, and receiving)
235         zero = ZERO[self.main_currency]
236         for take in takes:
237             member_id = take.member
238             old_amount = take.actual_amount or zero
239             new_amount = takes_sum.get(take.member, zero)
240             diff = new_amount - old_amount
241             if diff != 0:
242                 take.actual_amount = new_amount
243                 cursor.run("""
244                     UPDATE takes
245                        SET actual_amount = %(actual_amount)s
246                      WHERE id = %(id)s
247                 """, take.__dict__)
248                 ntippers = len(tippers.get(member_id, ()))
249                 member_currency, old_taking = cursor.one(
250                     "SELECT main_currency, taking FROM participants WHERE id = %s", (member_id,)
251                 )
252                 diff = diff.convert(member_currency)
253                 if old_taking + diff < 0:
254                     # Make sure currency fluctuation doesn't result in a negative number
255                     diff = -old_taking
256                 cursor.run("""
257                     UPDATE participants
258                        SET taking = (taking + %(diff)s)
259                          , receiving = (receiving + %(diff)s)
260                          , nteampatrons = (
261                                CASE WHEN (receiving + %(diff)s) = 0 THEN 0
262                                     WHEN nteampatrons < %(ntippers)s THEN %(ntippers)s
263                                     ELSE nteampatrons
264                                END
265                            )
266                      WHERE id=%(member_id)s
267                 """, dict(member_id=member_id, diff=diff, ntippers=ntippers))
268             if member and member.id == member_id:
269                 r = cursor.one(
270                     "SELECT taking, receiving FROM participants WHERE id = %s",
271                     (member_id,)
272                 )
273                 member.set_attributes(**r._asdict())
274         return takes
275 
276     @property
277     def nmembers(self):
278         assert self.kind == 'group'
279         return self.db.one("""
280             SELECT COUNT(*)
281               FROM current_takes
282              WHERE team=%s
283         """, (self.id,))
284 
285     def get_members(self):
286         """Return an OrderedDict of member dicts.
287         """
288         takes = self.get_current_takes()
289         last_week = self.get_takes_last_week()
290         members = OrderedDict()
291         members.leftover = self.leftover
292         zero = ZERO[self.main_currency]
293         for take in takes:
294             member = {}
295             m_id = member['id'] = take['member_id']
296             member['username'] = take['member_name']
297             member['nominal_take'] = take['amount'].amount
298             member['actual_amount'] = take['actual_amount']
299             member['last_week'] = last_week.get(m_id, zero).amount
300             x = self.compute_max_this_week(m_id, last_week)
301             member['max_this_week'] = x
302             members[member['id']] = member
303         return members
304 
305     @property
306     def closed_by(self):
307         assert self.status == 'closed'
308         return self.db.one("""
309             SELECT member
310               FROM takes
311              WHERE team = %s
312           ORDER BY mtime DESC
313              LIMIT 1
314         """, (self.id,))
315
```
--- 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/liberapay/models/_mixin_team.py b/liberapay/models/_mixin_team.py
--- a/liberapay/models/_mixin_team.py
+++ b/liberapay/models/_mixin_team.py
@@ -118,8 +118,7 @@
             if cur_take is None:
                 return None
 
-        if not isinstance(take, (None.__class__, Money)):
-            take = Money(take, self.main_currency)  # TODO drop this
+        assert isinstance(take, (None.__class__, Money))
 
         with self.db.get_cursor(cursor) as cursor:
             # Lock to avoid race conditions

{"golden_diff": "diff --git a/liberapay/models/_mixin_team.py b/liberapay/models/_mixin_team.py\n--- a/liberapay/models/_mixin_team.py\n+++ b/liberapay/models/_mixin_team.py\n@@ -118,8 +118,7 @@\n             if cur_take is None:\n                 return None\n \n-        if not isinstance(take, (None.__class__, Money)):\n-            take = Money(take, self.main_currency)  # TODO drop this\n+        assert isinstance(take, (None.__class__, Money))\n \n         with self.db.get_cursor(cursor) as cursor:\n             # Lock to avoid race conditions\n", "issue": "Currency exchange rate between EUR and USD are mixed up\nIf I give euros to an account using dollar as main currency, the currency rate gets mixed up somewhere.\r\nFor instance if I give 5\u20ac, on the creator's page it only displays 4.05$ instead of 6.18$.\r\n\r\nCurrent exchange rate:\r\n* 5 EUR to USD: 6.18$\r\n* 5 USD to EUR: 4.05\u20ac\n", "before_files": [{"content": "\"\"\"Teams are groups of participants.\n\"\"\"\nfrom __future__ import division, print_function, unicode_literals\n\nfrom collections import OrderedDict\nfrom statistics import median\n\nfrom mangopay.utils import Money\n\nfrom liberapay.constants import ZERO, TAKE_THROTTLING_THRESHOLD\nfrom liberapay.utils import NS\n\n\nclass MemberLimitReached(Exception): pass\n\n\nclass InactiveParticipantAdded(Exception): pass\n\n\nclass MixinTeam(object):\n\n    def invite(self, invitee, inviter):\n        assert self.kind == 'group'\n        with self.db.get_cursor() as c:\n            n_id = invitee.notify(\n                'team_invite',\n                team=self.username,\n                team_url=self.url(),\n                inviter=inviter.username,\n            )\n            payload = dict(invitee=invitee.id, notification_id=n_id)\n            self.add_event(c, 'invite', payload, inviter.id)\n\n    def add_member(self, member, cursor=None):\n        \"\"\"Add a member to this team.\n        \"\"\"\n        if self.nmembers >= 149:\n            raise MemberLimitReached\n        if member.status != 'active':\n            raise InactiveParticipantAdded\n        self.set_take_for(member, ZERO[self.main_currency], self, cursor=cursor)\n\n    def remove_all_members(self, cursor=None):\n        (cursor or self.db).run(\"\"\"\n            INSERT INTO takes\n                        (ctime, member, team, amount, actual_amount, recorder)\n                 SELECT ctime, member, %(id)s, NULL, NULL, %(id)s\n                   FROM current_takes\n                  WHERE team=%(id)s\n        \"\"\", dict(id=self.id))\n\n    def member_of(self, team):\n        \"\"\"Given a Participant object, return a boolean.\n        \"\"\"\n        assert team.kind == 'group'\n        return self.db.one(\"\"\"\n            SELECT true\n              FROM current_takes\n             WHERE team=%s AND member=%s\n        \"\"\", (team.id, self.id), default=False)\n\n    def get_takes_last_week(self):\n        \"\"\"Get the users' nominal takes last week. Used in throttling.\n        \"\"\"\n        assert self.kind == 'group'\n        takes = {t.member: t.amount for t in self.db.all(\"\"\"\n\n            SELECT DISTINCT ON (member) member, amount, mtime\n              FROM takes\n             WHERE team=%s\n               AND mtime < (\n                       SELECT ts_start\n                         FROM paydays\n                        WHERE ts_end > ts_start\n                     ORDER BY ts_start DESC LIMIT 1\n                   )\n          ORDER BY member, mtime DESC\n\n        \"\"\", (self.id,)) if t.amount}\n        return takes\n\n    def get_take_for(self, member):\n        \"\"\"Return the nominal take for this member, or None.\n        \"\"\"\n        return self.db.one(\n            \"SELECT amount FROM current_takes WHERE member = %s AND team = %s\",\n            (member.id, self.id)\n        )\n\n    def compute_max_this_week(self, member_id, last_week):\n        \"\"\"2x the member's take last week, or the member's take last week + the\n        leftover, or last week's median take, or 1.00, or infinity if the takes\n        were all zero last week or if throttling is disabled.\n        \"\"\"\n        if not self.throttle_takes:\n            return\n        sum_last_week = sum(last_week.values())\n        if sum_last_week == 0:\n            return\n        sum_last_week = sum_last_week.convert(self.main_currency)\n        initial_leftover = self.receiving - sum_last_week\n        nonzero_last_week = [a.convert(self.main_currency).amount for a in last_week.values() if a]\n        member_last_week = last_week.get(member_id, ZERO[self.main_currency]).convert(self.main_currency)\n        return max(\n            member_last_week * 2,\n            member_last_week + initial_leftover,\n            Money(median(nonzero_last_week or (0,)), self.main_currency),\n            TAKE_THROTTLING_THRESHOLD[self.main_currency]\n        )\n\n    def set_take_for(self, member, take, recorder, check_max=True, cursor=None):\n        \"\"\"Sets member's take from the team pool.\n        \"\"\"\n        assert self.kind == 'group'\n\n        if recorder.id != self.id:\n            cur_take = self.get_take_for(member)\n            if cur_take is None:\n                return None\n\n        if not isinstance(take, (None.__class__, Money)):\n            take = Money(take, self.main_currency)  # TODO drop this\n\n        with self.db.get_cursor(cursor) as cursor:\n            # Lock to avoid race conditions\n            cursor.run(\"LOCK TABLE takes IN EXCLUSIVE MODE\")\n            # Throttle the new take, if there is more than one member\n            threshold = TAKE_THROTTLING_THRESHOLD[self.main_currency]\n            if take and check_max and self.nmembers > 1 and take > threshold:\n                last_week = self.get_takes_last_week()\n                max_this_week = self.compute_max_this_week(member.id, last_week)\n                if max_this_week is not None and take > max_this_week:\n                    take = max_this_week\n            # Insert the new take\n            cursor.run(\"\"\"\n\n                INSERT INTO takes\n                            (ctime, member, team, amount, actual_amount, recorder)\n                     SELECT COALESCE((\n                                SELECT ctime\n                                  FROM takes\n                                 WHERE member=%(member)s\n                                   AND team=%(team)s\n                                 LIMIT 1\n                            ), current_timestamp)\n                          , %(member)s\n                          , %(team)s\n                          , %(amount)s\n                          , CASE WHEN %(amount)s IS NULL THEN NULL ELSE\n                                COALESCE((\n                                    SELECT actual_amount\n                                      FROM takes\n                                     WHERE member=%(member)s\n                                       AND team=%(team)s\n                                  ORDER BY mtime DESC\n                                     LIMIT 1\n                                ), zero(%(amount)s::currency_amount))\n                            END\n                          , %(recorder)s\n\n            \"\"\", dict(member=member.id, team=self.id, amount=take,\n                      recorder=recorder.id))\n            # Recompute the actual takes and update the cached amounts\n            self.recompute_actual_takes(cursor, member=member)\n            # Update is_funded on member's tips\n            member.update_giving(cursor)\n\n        return take\n\n    def get_current_takes(self, cursor=None):\n        \"\"\"Return a list of member takes for a team.\n        \"\"\"\n        assert self.kind == 'group'\n        TAKES = \"\"\"\n            SELECT p.id AS member_id, p.username AS member_name, p.avatar_url\n                 , (p.mangopay_user_id IS NOT NULL) AS is_identified, p.is_suspended\n                 , t.amount, t.actual_amount, t.ctime, t.mtime\n              FROM current_takes t\n              JOIN participants p ON p.id = member\n             WHERE t.team=%(team)s\n          ORDER BY p.username\n        \"\"\"\n        records = (cursor or self.db).all(TAKES, dict(team=self.id))\n        return [r._asdict() for r in records]\n\n    def recompute_actual_takes(self, cursor, member=None):\n        \"\"\"Get the tips and takes for this team and recompute the actual amounts.\n\n        To avoid deadlocks the given `cursor` should have already acquired an\n        exclusive lock on the `takes` table.\n        \"\"\"\n        from liberapay.billing.payday import Payday\n        tips = [NS(t._asdict()) for t in cursor.all(\"\"\"\n            SELECT t.id, t.tipper, t.amount AS full_amount\n                 , coalesce_currency_amount((\n                       SELECT sum(tr.amount, t.amount::currency)\n                         FROM transfers tr\n                        WHERE tr.tipper = t.tipper\n                          AND tr.team = %(team_id)s\n                          AND tr.context = 'take'\n                          AND tr.status = 'succeeded'\n                   ), t.amount::currency) AS past_transfers_sum\n              FROM current_tips t\n              JOIN participants p ON p.id = t.tipper\n             WHERE t.tippee = %(team_id)s\n               AND t.is_funded\n               AND p.is_suspended IS NOT true\n        \"\"\", dict(team_id=self.id))]\n        takes = [NS(r._asdict()) for r in (cursor or self.db).all(\"\"\"\n            SELECT t.*\n              FROM current_takes t\n              JOIN participants p ON p.id = t.member\n             WHERE t.team = %s\n               AND p.is_suspended IS NOT true\n               AND p.mangopay_user_id IS NOT NULL\n        \"\"\", (self.id,))]\n        # Recompute the takes\n        takes_sum = {}\n        tippers = {}\n        transfers, new_leftover = Payday.resolve_takes(tips, takes, self.main_currency)\n        for t in transfers:\n            if t.member in takes_sum:\n                takes_sum[t.member] += t.amount\n            else:\n                takes_sum[t.member] = t.amount\n            if t.member in tippers:\n                tippers[t.member].add(t.tipper)\n            else:\n                tippers[t.member] = set((t.tipper,))\n        # Update the leftover\n        cursor.run(\"UPDATE participants SET leftover = %s WHERE id = %s\",\n                   (new_leftover, self.id))\n        self.set_attributes(leftover=new_leftover)\n        # Update the cached amounts (actual_amount, taking, and receiving)\n        zero = ZERO[self.main_currency]\n        for take in takes:\n            member_id = take.member\n            old_amount = take.actual_amount or zero\n            new_amount = takes_sum.get(take.member, zero)\n            diff = new_amount - old_amount\n            if diff != 0:\n                take.actual_amount = new_amount\n                cursor.run(\"\"\"\n                    UPDATE takes\n                       SET actual_amount = %(actual_amount)s\n                     WHERE id = %(id)s\n                \"\"\", take.__dict__)\n                ntippers = len(tippers.get(member_id, ()))\n                member_currency, old_taking = cursor.one(\n                    \"SELECT main_currency, taking FROM participants WHERE id = %s\", (member_id,)\n                )\n                diff = diff.convert(member_currency)\n                if old_taking + diff < 0:\n                    # Make sure currency fluctuation doesn't result in a negative number\n                    diff = -old_taking\n                cursor.run(\"\"\"\n                    UPDATE participants\n                       SET taking = (taking + %(diff)s)\n                         , receiving = (receiving + %(diff)s)\n                         , nteampatrons = (\n                               CASE WHEN (receiving + %(diff)s) = 0 THEN 0\n                                    WHEN nteampatrons < %(ntippers)s THEN %(ntippers)s\n                                    ELSE nteampatrons\n                               END\n                           )\n                     WHERE id=%(member_id)s\n                \"\"\", dict(member_id=member_id, diff=diff, ntippers=ntippers))\n            if member and member.id == member_id:\n                r = cursor.one(\n                    \"SELECT taking, receiving FROM participants WHERE id = %s\",\n                    (member_id,)\n                )\n                member.set_attributes(**r._asdict())\n        return takes\n\n    @property\n    def nmembers(self):\n        assert self.kind == 'group'\n        return self.db.one(\"\"\"\n            SELECT COUNT(*)\n              FROM current_takes\n             WHERE team=%s\n        \"\"\", (self.id,))\n\n    def get_members(self):\n        \"\"\"Return an OrderedDict of member dicts.\n        \"\"\"\n        takes = self.get_current_takes()\n        last_week = self.get_takes_last_week()\n        members = OrderedDict()\n        members.leftover = self.leftover\n        zero = ZERO[self.main_currency]\n        for take in takes:\n            member = {}\n            m_id = member['id'] = take['member_id']\n            member['username'] = take['member_name']\n            member['nominal_take'] = take['amount'].amount\n            member['actual_amount'] = take['actual_amount']\n            member['last_week'] = last_week.get(m_id, zero).amount\n            x = self.compute_max_this_week(m_id, last_week)\n            member['max_this_week'] = x\n            members[member['id']] = member\n        return members\n\n    @property\n    def closed_by(self):\n        assert self.status == 'closed'\n        return self.db.one(\"\"\"\n            SELECT member\n              FROM takes\n             WHERE team = %s\n          ORDER BY mtime DESC\n             LIMIT 1\n        \"\"\", (self.id,))\n", "path": "liberapay/models/_mixin_team.py"}], "after_files": [{"content": "\"\"\"Teams are groups of participants.\n\"\"\"\nfrom __future__ import division, print_function, unicode_literals\n\nfrom collections import OrderedDict\nfrom statistics import median\n\nfrom mangopay.utils import Money\n\nfrom liberapay.constants import ZERO, TAKE_THROTTLING_THRESHOLD\nfrom liberapay.utils import NS\n\n\nclass MemberLimitReached(Exception): pass\n\n\nclass InactiveParticipantAdded(Exception): pass\n\n\nclass MixinTeam(object):\n\n    def invite(self, invitee, inviter):\n        assert self.kind == 'group'\n        with self.db.get_cursor() as c:\n            n_id = invitee.notify(\n                'team_invite',\n                team=self.username,\n                team_url=self.url(),\n                inviter=inviter.username,\n            )\n            payload = dict(invitee=invitee.id, notification_id=n_id)\n            self.add_event(c, 'invite', payload, inviter.id)\n\n    def add_member(self, member, cursor=None):\n        \"\"\"Add a member to this team.\n        \"\"\"\n        if self.nmembers >= 149:\n            raise MemberLimitReached\n        if member.status != 'active':\n            raise InactiveParticipantAdded\n        self.set_take_for(member, ZERO[self.main_currency], self, cursor=cursor)\n\n    def remove_all_members(self, cursor=None):\n        (cursor or self.db).run(\"\"\"\n            INSERT INTO takes\n                        (ctime, member, team, amount, actual_amount, recorder)\n                 SELECT ctime, member, %(id)s, NULL, NULL, %(id)s\n                   FROM current_takes\n                  WHERE team=%(id)s\n        \"\"\", dict(id=self.id))\n\n    def member_of(self, team):\n        \"\"\"Given a Participant object, return a boolean.\n        \"\"\"\n        assert team.kind == 'group'\n        return self.db.one(\"\"\"\n            SELECT true\n              FROM current_takes\n             WHERE team=%s AND member=%s\n        \"\"\", (team.id, self.id), default=False)\n\n    def get_takes_last_week(self):\n        \"\"\"Get the users' nominal takes last week. Used in throttling.\n        \"\"\"\n        assert self.kind == 'group'\n        takes = {t.member: t.amount for t in self.db.all(\"\"\"\n\n            SELECT DISTINCT ON (member) member, amount, mtime\n              FROM takes\n             WHERE team=%s\n               AND mtime < (\n                       SELECT ts_start\n                         FROM paydays\n                        WHERE ts_end > ts_start\n                     ORDER BY ts_start DESC LIMIT 1\n                   )\n          ORDER BY member, mtime DESC\n\n        \"\"\", (self.id,)) if t.amount}\n        return takes\n\n    def get_take_for(self, member):\n        \"\"\"Return the nominal take for this member, or None.\n        \"\"\"\n        return self.db.one(\n            \"SELECT amount FROM current_takes WHERE member = %s AND team = %s\",\n            (member.id, self.id)\n        )\n\n    def compute_max_this_week(self, member_id, last_week):\n        \"\"\"2x the member's take last week, or the member's take last week + the\n        leftover, or last week's median take, or 1.00, or infinity if the takes\n        were all zero last week or if throttling is disabled.\n        \"\"\"\n        if not self.throttle_takes:\n            return\n        sum_last_week = sum(last_week.values())\n        if sum_last_week == 0:\n            return\n        sum_last_week = sum_last_week.convert(self.main_currency)\n        initial_leftover = self.receiving - sum_last_week\n        nonzero_last_week = [a.convert(self.main_currency).amount for a in last_week.values() if a]\n        member_last_week = last_week.get(member_id, ZERO[self.main_currency]).convert(self.main_currency)\n        return max(\n            member_last_week * 2,\n            member_last_week + initial_leftover,\n            Money(median(nonzero_last_week or (0,)), self.main_currency),\n            TAKE_THROTTLING_THRESHOLD[self.main_currency]\n        )\n\n    def set_take_for(self, member, take, recorder, check_max=True, cursor=None):\n        \"\"\"Sets member's take from the team pool.\n        \"\"\"\n        assert self.kind == 'group'\n\n        if recorder.id != self.id:\n            cur_take = self.get_take_for(member)\n            if cur_take is None:\n                return None\n\n        assert isinstance(take, (None.__class__, Money))\n\n        with self.db.get_cursor(cursor) as cursor:\n            # Lock to avoid race conditions\n            cursor.run(\"LOCK TABLE takes IN EXCLUSIVE MODE\")\n            # Throttle the new take, if there is more than one member\n            threshold = TAKE_THROTTLING_THRESHOLD[self.main_currency]\n            if take and check_max and self.nmembers > 1 and take > threshold:\n                last_week = self.get_takes_last_week()\n                max_this_week = self.compute_max_this_week(member.id, last_week)\n                if max_this_week is not None and take > max_this_week:\n                    take = max_this_week\n            # Insert the new take\n            cursor.run(\"\"\"\n\n                INSERT INTO takes\n                            (ctime, member, team, amount, actual_amount, recorder)\n                     SELECT COALESCE((\n                                SELECT ctime\n                                  FROM takes\n                                 WHERE member=%(member)s\n                                   AND team=%(team)s\n                                 LIMIT 1\n                            ), current_timestamp)\n                          , %(member)s\n                          , %(team)s\n                          , %(amount)s\n                          , CASE WHEN %(amount)s IS NULL THEN NULL ELSE\n                                COALESCE((\n                                    SELECT actual_amount\n                                      FROM takes\n                                     WHERE member=%(member)s\n                                       AND team=%(team)s\n                                  ORDER BY mtime DESC\n                                     LIMIT 1\n                                ), zero(%(amount)s::currency_amount))\n                            END\n                          , %(recorder)s\n\n            \"\"\", dict(member=member.id, team=self.id, amount=take,\n                      recorder=recorder.id))\n            # Recompute the actual takes and update the cached amounts\n            self.recompute_actual_takes(cursor, member=member)\n            # Update is_funded on member's tips\n            member.update_giving(cursor)\n\n        return take\n\n    def get_current_takes(self, cursor=None):\n        \"\"\"Return a list of member takes for a team.\n        \"\"\"\n        assert self.kind == 'group'\n        TAKES = \"\"\"\n            SELECT p.id AS member_id, p.username AS member_name, p.avatar_url\n                 , (p.mangopay_user_id IS NOT NULL) AS is_identified, p.is_suspended\n                 , t.amount, t.actual_amount, t.ctime, t.mtime\n              FROM current_takes t\n              JOIN participants p ON p.id = member\n             WHERE t.team=%(team)s\n          ORDER BY p.username\n        \"\"\"\n        records = (cursor or self.db).all(TAKES, dict(team=self.id))\n        return [r._asdict() for r in records]\n\n    def recompute_actual_takes(self, cursor, member=None):\n        \"\"\"Get the tips and takes for this team and recompute the actual amounts.\n\n        To avoid deadlocks the given `cursor` should have already acquired an\n        exclusive lock on the `takes` table.\n        \"\"\"\n        from liberapay.billing.payday import Payday\n        tips = [NS(t._asdict()) for t in cursor.all(\"\"\"\n            SELECT t.id, t.tipper, t.amount AS full_amount\n                 , coalesce_currency_amount((\n                       SELECT sum(tr.amount, t.amount::currency)\n                         FROM transfers tr\n                        WHERE tr.tipper = t.tipper\n                          AND tr.team = %(team_id)s\n                          AND tr.context = 'take'\n                          AND tr.status = 'succeeded'\n                   ), t.amount::currency) AS past_transfers_sum\n              FROM current_tips t\n              JOIN participants p ON p.id = t.tipper\n             WHERE t.tippee = %(team_id)s\n               AND t.is_funded\n               AND p.is_suspended IS NOT true\n        \"\"\", dict(team_id=self.id))]\n        takes = [NS(r._asdict()) for r in (cursor or self.db).all(\"\"\"\n            SELECT t.*\n              FROM current_takes t\n              JOIN participants p ON p.id = t.member\n             WHERE t.team = %s\n               AND p.is_suspended IS NOT true\n               AND p.mangopay_user_id IS NOT NULL\n        \"\"\", (self.id,))]\n        # Recompute the takes\n        takes_sum = {}\n        tippers = {}\n        transfers, new_leftover = Payday.resolve_takes(tips, takes, self.main_currency)\n        for t in transfers:\n            if t.member in takes_sum:\n                takes_sum[t.member] += t.amount\n            else:\n                takes_sum[t.member] = t.amount\n            if t.member in tippers:\n                tippers[t.member].add(t.tipper)\n            else:\n                tippers[t.member] = set((t.tipper,))\n        # Update the leftover\n        cursor.run(\"UPDATE participants SET leftover = %s WHERE id = %s\",\n                   (new_leftover, self.id))\n        self.set_attributes(leftover=new_leftover)\n        # Update the cached amounts (actual_amount, taking, and receiving)\n        zero = ZERO[self.main_currency]\n        for take in takes:\n            member_id = take.member\n            old_amount = take.actual_amount or zero\n            new_amount = takes_sum.get(take.member, zero)\n            diff = new_amount - old_amount\n            if diff != 0:\n                take.actual_amount = new_amount\n                cursor.run(\"\"\"\n                    UPDATE takes\n                       SET actual_amount = %(actual_amount)s\n                     WHERE id = %(id)s\n                \"\"\", take.__dict__)\n                ntippers = len(tippers.get(member_id, ()))\n                member_currency, old_taking = cursor.one(\n                    \"SELECT main_currency, taking FROM participants WHERE id = %s\", (member_id,)\n                )\n                diff = diff.convert(member_currency)\n                if old_taking + diff < 0:\n                    # Make sure currency fluctuation doesn't result in a negative number\n                    diff = -old_taking\n                cursor.run(\"\"\"\n                    UPDATE participants\n                       SET taking = (taking + %(diff)s)\n                         , receiving = (receiving + %(diff)s)\n                         , nteampatrons = (\n                               CASE WHEN (receiving + %(diff)s) = 0 THEN 0\n                                    WHEN nteampatrons < %(ntippers)s THEN %(ntippers)s\n                                    ELSE nteampatrons\n                               END\n                           )\n                     WHERE id=%(member_id)s\n                \"\"\", dict(member_id=member_id, diff=diff, ntippers=ntippers))\n            if member and member.id == member_id:\n                r = cursor.one(\n                    \"SELECT taking, receiving FROM participants WHERE id = %s\",\n                    (member_id,)\n                )\n                member.set_attributes(**r._asdict())\n        return takes\n\n    @property\n    def nmembers(self):\n        assert self.kind == 'group'\n        return self.db.one(\"\"\"\n            SELECT COUNT(*)\n              FROM current_takes\n             WHERE team=%s\n        \"\"\", (self.id,))\n\n    def get_members(self):\n        \"\"\"Return an OrderedDict of member dicts.\n        \"\"\"\n        takes = self.get_current_takes()\n        last_week = self.get_takes_last_week()\n        members = OrderedDict()\n        members.leftover = self.leftover\n        zero = ZERO[self.main_currency]\n        for take in takes:\n            member = {}\n            m_id = member['id'] = take['member_id']\n            member['username'] = take['member_name']\n            member['nominal_take'] = take['amount'].amount\n            member['actual_amount'] = take['actual_amount']\n            member['last_week'] = last_week.get(m_id, zero).amount\n            x = self.compute_max_this_week(m_id, last_week)\n            member['max_this_week'] = x\n            members[member['id']] = member\n        return members\n\n    @property\n    def closed_by(self):\n        assert self.status == 'closed'\n        return self.db.one(\"\"\"\n            SELECT member\n              FROM takes\n             WHERE team = %s\n          ORDER BY mtime DESC\n             LIMIT 1\n        \"\"\", (self.id,))\n", "path": "liberapay/models/_mixin_team.py"}]}