We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Take throttling is too complicated
https://witches.town/@Alda/2122717

--- 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 decimal import Decimal, ROUND_UP
7 from statistics import median
8 
9 from liberapay.constants import D_CENT, D_INF, D_UNIT, D_ZERO
10 
11 
12 class MemberLimitReached(Exception): pass
13 
14 
15 class InactiveParticipantAdded(Exception): pass
16 
17 
18 class MixinTeam(object):
19 
20     def invite(self, invitee, inviter):
21         assert self.kind == 'group'
22         with self.db.get_cursor() as c:
23             n_id = invitee.notify(
24                 'team_invite',
25                 team=self.username,
26                 team_url=self.url(),
27                 inviter=inviter.username,
28             )
29             payload = dict(invitee=invitee.id, notification_id=n_id)
30             self.add_event(c, 'invite', payload, inviter.id)
31 
32     def add_member(self, member, cursor=None):
33         """Add a member to this team.
34         """
35         if len(self.get_current_takes()) == 149:
36             raise MemberLimitReached
37         if member.status != 'active':
38             raise InactiveParticipantAdded
39         self.set_take_for(member, D_ZERO, self, cursor=cursor)
40 
41     def remove_all_members(self, cursor=None):
42         (cursor or self.db).run("""
43             INSERT INTO takes (ctime, member, team, amount, recorder) (
44                 SELECT ctime, member, %(id)s, NULL, %(id)s
45                   FROM current_takes
46                  WHERE team=%(id)s
47             );
48         """, dict(id=self.id))
49 
50     def member_of(self, team):
51         """Given a Participant object, return a boolean.
52         """
53         assert team.kind == 'group'
54         return self.db.one("""
55             SELECT true
56               FROM current_takes
57              WHERE team=%s AND member=%s
58         """, (team.id, self.id), default=False)
59 
60     def get_takes_last_week(self):
61         """Get the users' nominal takes last week. Used in throttling.
62         """
63         assert self.kind == 'group'
64         takes = {t.member: t.amount for t in self.db.all("""
65 
66             SELECT DISTINCT ON (member) member, amount, mtime
67               FROM takes
68              WHERE team=%s
69                AND mtime < (
70                        SELECT ts_start
71                          FROM paydays
72                         WHERE ts_end > ts_start
73                      ORDER BY ts_start DESC LIMIT 1
74                    )
75           ORDER BY member, mtime DESC
76 
77         """, (self.id,)) if t.amount}
78         return takes
79 
80     def get_take_for(self, member):
81         """Return the nominal take for this member, or None.
82         """
83         return self.db.one(
84             "SELECT amount FROM current_takes WHERE member = %s AND team = %s",
85             (member.id, self.id)
86         )
87 
88     def compute_max_this_week(self, member_id, last_week):
89         """2x the member's take last week, or the member's take last week + a
90         proportional share of the leftover, or a minimum based on last week's
91         median take, or 1.
92         """
93         sum_last_week = sum(last_week.values())
94         initial_leftover = self.receiving - sum_last_week
95         nonzero_last_week = [a for a in last_week.values() if a]
96         member_last_week = last_week.get(member_id, 0)
97         leftover_share = member_last_week / (sum_last_week or D_INF)
98         leftover_share = max(leftover_share, D_UNIT / self.nmembers)
99         return max(
100             member_last_week * 2,
101             member_last_week + initial_leftover * leftover_share,
102             median(nonzero_last_week or (0,)),
103             D_UNIT
104         )
105 
106     def set_take_for(self, member, take, recorder, check_max=True, cursor=None):
107         """Sets member's take from the team pool.
108         """
109         assert self.kind == 'group'
110 
111         if recorder.id != self.id:
112             cur_take = self.get_take_for(member)
113             if cur_take is None:
114                 return None
115 
116         if not isinstance(take, (None.__class__, Decimal)):
117             take = Decimal(take)
118 
119         if take and check_max and take > 1:
120             last_week = self.get_takes_last_week()
121             max_this_week = self.compute_max_this_week(member.id, last_week)
122             if take > max_this_week:
123                 take = max_this_week
124 
125         with self.db.get_cursor(cursor) as cursor:
126             # Lock to avoid race conditions
127             cursor.run("LOCK TABLE takes IN EXCLUSIVE MODE")
128             # Compute the current takes
129             old_takes = self.compute_actual_takes(cursor)
130             # Insert the new take
131             cursor.run("""
132 
133                 INSERT INTO takes (ctime, member, team, amount, recorder)
134                  VALUES ( COALESCE (( SELECT ctime
135                                         FROM takes
136                                        WHERE member=%(member)s
137                                          AND team=%(team)s
138                                        LIMIT 1
139                                      ), CURRENT_TIMESTAMP)
140                         , %(member)s
141                         , %(team)s
142                         , %(amount)s
143                         , %(recorder)s
144                          )
145 
146             """, dict(member=member.id, team=self.id, amount=take,
147                       recorder=recorder.id))
148             # Compute the new takes
149             new_takes = self.compute_actual_takes(cursor)
150             # Update receiving amounts in the participants table
151             self.update_taking(old_takes, new_takes, cursor, member)
152             # Update is_funded on member's tips
153             member.update_giving(cursor)
154 
155         return take
156 
157     def update_taking(self, old_takes, new_takes, cursor=None, member=None):
158         """Update `taking` amounts based on the difference between `old_takes`
159         and `new_takes`.
160         """
161         for p_id in set(old_takes.keys()).union(new_takes.keys()):
162             old = old_takes.get(p_id, {}).get('actual_amount', D_ZERO)
163             new = new_takes.get(p_id, {}).get('actual_amount', D_ZERO)
164             diff = new - old
165             if diff != 0:
166                 (cursor or self.db).run("""
167                     UPDATE participants
168                        SET taking = (taking + %(diff)s)
169                          , receiving = (receiving + %(diff)s)
170                      WHERE id=%(p_id)s
171                 """, dict(p_id=p_id, diff=diff))
172             if member and p_id == member.id:
173                 r = (cursor or self.db).one(
174                     "SELECT taking, receiving FROM participants WHERE id = %s",
175                     (p_id,)
176                 )
177                 member.set_attributes(**r._asdict())
178 
179     def get_current_takes(self, cursor=None):
180         """Return a list of member takes for a team.
181         """
182         assert self.kind == 'group'
183         TAKES = """
184             SELECT p.id AS member_id, p.username AS member_name, p.avatar_url
185                  , (p.mangopay_user_id IS NOT NULL) AS is_identified
186                  , t.amount, t.ctime, t.mtime
187               FROM current_takes t
188               JOIN participants p ON p.id = member
189              WHERE t.team=%(team)s
190           ORDER BY p.username
191         """
192         records = (cursor or self.db).all(TAKES, dict(team=self.id))
193         return [r._asdict() for r in records]
194 
195     def compute_actual_takes(self, cursor=None):
196         """Get the takes, compute the actual amounts, and return an OrderedDict.
197         """
198         actual_takes = OrderedDict()
199         nominal_takes = self.get_current_takes(cursor=cursor)
200         balance = self.receiving
201         total_takes = sum(t['amount'] for t in nominal_takes if t['is_identified'])
202         ratio = min(balance / total_takes, 1) if total_takes else 0
203         for take in nominal_takes:
204             nominal = take['nominal_take'] = take.pop('amount')
205             actual = take['actual_amount'] = min(
206                 (nominal * ratio).quantize(D_CENT, rounding=ROUND_UP),
207                 balance
208             ) if take['is_identified'] else D_ZERO
209             balance -= actual
210             actual_takes[take['member_id']] = take
211         actual_takes.leftover = balance
212         return actual_takes
213 
214     @property
215     def nmembers(self):
216         assert self.kind == 'group'
217         return self.db.one("""
218             SELECT COUNT(*)
219               FROM current_takes
220              WHERE team=%s
221         """, (self.id,))
222 
223     def get_members(self):
224         """Return an OrderedDict of member dicts.
225         """
226         takes = self.compute_actual_takes()
227         last_week = self.get_takes_last_week()
228         members = OrderedDict()
229         members.leftover = takes.leftover
230         for take in takes.values():
231             member = {}
232             m_id = member['id'] = take['member_id']
233             member['username'] = take['member_name']
234             member['nominal_take'] = take['nominal_take']
235             member['actual_amount'] = take['actual_amount']
236             member['last_week'] = last_week.get(m_id, D_ZERO)
237             member['max_this_week'] = self.compute_max_this_week(m_id, last_week)
238             members[member['id']] = member
239         return members
240 
241     @property
242     def closed_by(self):
243         assert self.status == 'closed'
244         return self.db.one("""
245             SELECT member
246               FROM takes
247              WHERE team = %s
248           ORDER BY mtime DESC
249              LIMIT 1
250         """, (self.id,))
251
```
--- 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
@@ -86,19 +86,21 @@
         )
 
     def compute_max_this_week(self, member_id, last_week):
-        """2x the member's take last week, or the member's take last week + a
-        proportional share of the leftover, or a minimum based on last week's
-        median take, or 1.
+        """2x the member's take last week, or the member's take last week + the
+        leftover, or last week's median take, or 1.00, or infinity if the takes
+        were all zero last week or if throttling is disabled.
         """
+        if not self.throttle_takes:
+            return D_INF
         sum_last_week = sum(last_week.values())
+        if sum_last_week == 0:
+            return D_INF
         initial_leftover = self.receiving - sum_last_week
         nonzero_last_week = [a for a in last_week.values() if a]
         member_last_week = last_week.get(member_id, 0)
-        leftover_share = member_last_week / (sum_last_week or D_INF)
-        leftover_share = max(leftover_share, D_UNIT / self.nmembers)
         return max(
             member_last_week * 2,
-            member_last_week + initial_leftover * leftover_share,
+            member_last_week + initial_leftover,
             median(nonzero_last_week or (0,)),
             D_UNIT
         )
@@ -116,17 +118,17 @@
         if not isinstance(take, (None.__class__, Decimal)):
             take = Decimal(take)
 
-        if take and check_max and take > 1:
-            last_week = self.get_takes_last_week()
-            max_this_week = self.compute_max_this_week(member.id, last_week)
-            if take > max_this_week:
-                take = max_this_week
-
         with self.db.get_cursor(cursor) as cursor:
             # Lock to avoid race conditions
             cursor.run("LOCK TABLE takes IN EXCLUSIVE MODE")
             # Compute the current takes
             old_takes = self.compute_actual_takes(cursor)
+            # Throttle the new take, if there is more than one member
+            if take and check_max and len(old_takes) > 1 and take > 1:
+                last_week = self.get_takes_last_week()
+                max_this_week = self.compute_max_this_week(member.id, last_week)
+                if take > max_this_week:
+                    take = max_this_week
             # Insert the new take
             cursor.run("""
 
@@ -234,7 +236,8 @@
             member['nominal_take'] = take['nominal_take']
             member['actual_amount'] = take['actual_amount']
             member['last_week'] = last_week.get(m_id, D_ZERO)
-            member['max_this_week'] = self.compute_max_this_week(m_id, last_week)
+            x = self.compute_max_this_week(m_id, last_week)
+            member['max_this_week'] = x if x.is_finite() else None
             members[member['id']] = member
         return members

{"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@@ -86,19 +86,21 @@\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 + a\n-        proportional share of the leftover, or a minimum based on last week's\n-        median take, or 1.\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 D_INF\n         sum_last_week = sum(last_week.values())\n+        if sum_last_week == 0:\n+            return D_INF\n         initial_leftover = self.receiving - sum_last_week\n         nonzero_last_week = [a for a in last_week.values() if a]\n         member_last_week = last_week.get(member_id, 0)\n-        leftover_share = member_last_week / (sum_last_week or D_INF)\n-        leftover_share = max(leftover_share, D_UNIT / self.nmembers)\n         return max(\n             member_last_week * 2,\n-            member_last_week + initial_leftover * leftover_share,\n+            member_last_week + initial_leftover,\n             median(nonzero_last_week or (0,)),\n             D_UNIT\n         )\n@@ -116,17 +118,17 @@\n         if not isinstance(take, (None.__class__, Decimal)):\n             take = Decimal(take)\n \n-        if take and check_max and take > 1:\n-            last_week = self.get_takes_last_week()\n-            max_this_week = self.compute_max_this_week(member.id, last_week)\n-            if take > max_this_week:\n-                take = max_this_week\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             # Compute the current takes\n             old_takes = self.compute_actual_takes(cursor)\n+            # Throttle the new take, if there is more than one member\n+            if take and check_max and len(old_takes) > 1 and take > 1:\n+                last_week = self.get_takes_last_week()\n+                max_this_week = self.compute_max_this_week(member.id, last_week)\n+                if take > max_this_week:\n+                    take = max_this_week\n             # Insert the new take\n             cursor.run(\"\"\"\n \n@@ -234,7 +236,8 @@\n             member['nominal_take'] = take['nominal_take']\n             member['actual_amount'] = take['actual_amount']\n             member['last_week'] = last_week.get(m_id, D_ZERO)\n-            member['max_this_week'] = self.compute_max_this_week(m_id, last_week)\n+            x = self.compute_max_this_week(m_id, last_week)\n+            member['max_this_week'] = x if x.is_finite() else None\n             members[member['id']] = member\n         return members\n", "issue": "Take throttling is too complicated\nhttps://witches.town/@Alda/2122717\n", "before_files": [{"content": "\"\"\"Teams are groups of participants.\n\"\"\"\nfrom __future__ import division, print_function, unicode_literals\n\nfrom collections import OrderedDict\nfrom decimal import Decimal, ROUND_UP\nfrom statistics import median\n\nfrom liberapay.constants import D_CENT, D_INF, D_UNIT, D_ZERO\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 len(self.get_current_takes()) == 149:\n            raise MemberLimitReached\n        if member.status != 'active':\n            raise InactiveParticipantAdded\n        self.set_take_for(member, D_ZERO, self, cursor=cursor)\n\n    def remove_all_members(self, cursor=None):\n        (cursor or self.db).run(\"\"\"\n            INSERT INTO takes (ctime, member, team, amount, recorder) (\n                SELECT ctime, member, %(id)s, NULL, %(id)s\n                  FROM current_takes\n                 WHERE team=%(id)s\n            );\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 + a\n        proportional share of the leftover, or a minimum based on last week's\n        median take, or 1.\n        \"\"\"\n        sum_last_week = sum(last_week.values())\n        initial_leftover = self.receiving - sum_last_week\n        nonzero_last_week = [a for a in last_week.values() if a]\n        member_last_week = last_week.get(member_id, 0)\n        leftover_share = member_last_week / (sum_last_week or D_INF)\n        leftover_share = max(leftover_share, D_UNIT / self.nmembers)\n        return max(\n            member_last_week * 2,\n            member_last_week + initial_leftover * leftover_share,\n            median(nonzero_last_week or (0,)),\n            D_UNIT\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__, Decimal)):\n            take = Decimal(take)\n\n        if take and check_max and take > 1:\n            last_week = self.get_takes_last_week()\n            max_this_week = self.compute_max_this_week(member.id, last_week)\n            if take > max_this_week:\n                take = max_this_week\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            # Compute the current takes\n            old_takes = self.compute_actual_takes(cursor)\n            # Insert the new take\n            cursor.run(\"\"\"\n\n                INSERT INTO takes (ctime, member, team, amount, recorder)\n                 VALUES ( COALESCE (( 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                        , %(recorder)s\n                         )\n\n            \"\"\", dict(member=member.id, team=self.id, amount=take,\n                      recorder=recorder.id))\n            # Compute the new takes\n            new_takes = self.compute_actual_takes(cursor)\n            # Update receiving amounts in the participants table\n            self.update_taking(old_takes, new_takes, cursor, member)\n            # Update is_funded on member's tips\n            member.update_giving(cursor)\n\n        return take\n\n    def update_taking(self, old_takes, new_takes, cursor=None, member=None):\n        \"\"\"Update `taking` amounts based on the difference between `old_takes`\n        and `new_takes`.\n        \"\"\"\n        for p_id in set(old_takes.keys()).union(new_takes.keys()):\n            old = old_takes.get(p_id, {}).get('actual_amount', D_ZERO)\n            new = new_takes.get(p_id, {}).get('actual_amount', D_ZERO)\n            diff = new - old\n            if diff != 0:\n                (cursor or self.db).run(\"\"\"\n                    UPDATE participants\n                       SET taking = (taking + %(diff)s)\n                         , receiving = (receiving + %(diff)s)\n                     WHERE id=%(p_id)s\n                \"\"\", dict(p_id=p_id, diff=diff))\n            if member and p_id == member.id:\n                r = (cursor or self.db).one(\n                    \"SELECT taking, receiving FROM participants WHERE id = %s\",\n                    (p_id,)\n                )\n                member.set_attributes(**r._asdict())\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\n                 , t.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 compute_actual_takes(self, cursor=None):\n        \"\"\"Get the takes, compute the actual amounts, and return an OrderedDict.\n        \"\"\"\n        actual_takes = OrderedDict()\n        nominal_takes = self.get_current_takes(cursor=cursor)\n        balance = self.receiving\n        total_takes = sum(t['amount'] for t in nominal_takes if t['is_identified'])\n        ratio = min(balance / total_takes, 1) if total_takes else 0\n        for take in nominal_takes:\n            nominal = take['nominal_take'] = take.pop('amount')\n            actual = take['actual_amount'] = min(\n                (nominal * ratio).quantize(D_CENT, rounding=ROUND_UP),\n                balance\n            ) if take['is_identified'] else D_ZERO\n            balance -= actual\n            actual_takes[take['member_id']] = take\n        actual_takes.leftover = balance\n        return actual_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.compute_actual_takes()\n        last_week = self.get_takes_last_week()\n        members = OrderedDict()\n        members.leftover = takes.leftover\n        for take in takes.values():\n            member = {}\n            m_id = member['id'] = take['member_id']\n            member['username'] = take['member_name']\n            member['nominal_take'] = take['nominal_take']\n            member['actual_amount'] = take['actual_amount']\n            member['last_week'] = last_week.get(m_id, D_ZERO)\n            member['max_this_week'] = self.compute_max_this_week(m_id, last_week)\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 decimal import Decimal, ROUND_UP\nfrom statistics import median\n\nfrom liberapay.constants import D_CENT, D_INF, D_UNIT, D_ZERO\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 len(self.get_current_takes()) == 149:\n            raise MemberLimitReached\n        if member.status != 'active':\n            raise InactiveParticipantAdded\n        self.set_take_for(member, D_ZERO, self, cursor=cursor)\n\n    def remove_all_members(self, cursor=None):\n        (cursor or self.db).run(\"\"\"\n            INSERT INTO takes (ctime, member, team, amount, recorder) (\n                SELECT ctime, member, %(id)s, NULL, %(id)s\n                  FROM current_takes\n                 WHERE team=%(id)s\n            );\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 D_INF\n        sum_last_week = sum(last_week.values())\n        if sum_last_week == 0:\n            return D_INF\n        initial_leftover = self.receiving - sum_last_week\n        nonzero_last_week = [a for a in last_week.values() if a]\n        member_last_week = last_week.get(member_id, 0)\n        return max(\n            member_last_week * 2,\n            member_last_week + initial_leftover,\n            median(nonzero_last_week or (0,)),\n            D_UNIT\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__, Decimal)):\n            take = Decimal(take)\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            # Compute the current takes\n            old_takes = self.compute_actual_takes(cursor)\n            # Throttle the new take, if there is more than one member\n            if take and check_max and len(old_takes) > 1 and take > 1:\n                last_week = self.get_takes_last_week()\n                max_this_week = self.compute_max_this_week(member.id, last_week)\n                if take > max_this_week:\n                    take = max_this_week\n            # Insert the new take\n            cursor.run(\"\"\"\n\n                INSERT INTO takes (ctime, member, team, amount, recorder)\n                 VALUES ( COALESCE (( 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                        , %(recorder)s\n                         )\n\n            \"\"\", dict(member=member.id, team=self.id, amount=take,\n                      recorder=recorder.id))\n            # Compute the new takes\n            new_takes = self.compute_actual_takes(cursor)\n            # Update receiving amounts in the participants table\n            self.update_taking(old_takes, new_takes, cursor, member)\n            # Update is_funded on member's tips\n            member.update_giving(cursor)\n\n        return take\n\n    def update_taking(self, old_takes, new_takes, cursor=None, member=None):\n        \"\"\"Update `taking` amounts based on the difference between `old_takes`\n        and `new_takes`.\n        \"\"\"\n        for p_id in set(old_takes.keys()).union(new_takes.keys()):\n            old = old_takes.get(p_id, {}).get('actual_amount', D_ZERO)\n            new = new_takes.get(p_id, {}).get('actual_amount', D_ZERO)\n            diff = new - old\n            if diff != 0:\n                (cursor or self.db).run(\"\"\"\n                    UPDATE participants\n                       SET taking = (taking + %(diff)s)\n                         , receiving = (receiving + %(diff)s)\n                     WHERE id=%(p_id)s\n                \"\"\", dict(p_id=p_id, diff=diff))\n            if member and p_id == member.id:\n                r = (cursor or self.db).one(\n                    \"SELECT taking, receiving FROM participants WHERE id = %s\",\n                    (p_id,)\n                )\n                member.set_attributes(**r._asdict())\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\n                 , t.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 compute_actual_takes(self, cursor=None):\n        \"\"\"Get the takes, compute the actual amounts, and return an OrderedDict.\n        \"\"\"\n        actual_takes = OrderedDict()\n        nominal_takes = self.get_current_takes(cursor=cursor)\n        balance = self.receiving\n        total_takes = sum(t['amount'] for t in nominal_takes if t['is_identified'])\n        ratio = min(balance / total_takes, 1) if total_takes else 0\n        for take in nominal_takes:\n            nominal = take['nominal_take'] = take.pop('amount')\n            actual = take['actual_amount'] = min(\n                (nominal * ratio).quantize(D_CENT, rounding=ROUND_UP),\n                balance\n            ) if take['is_identified'] else D_ZERO\n            balance -= actual\n            actual_takes[take['member_id']] = take\n        actual_takes.leftover = balance\n        return actual_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.compute_actual_takes()\n        last_week = self.get_takes_last_week()\n        members = OrderedDict()\n        members.leftover = takes.leftover\n        for take in takes.values():\n            member = {}\n            m_id = member['id'] = take['member_id']\n            member['username'] = take['member_name']\n            member['nominal_take'] = take['nominal_take']\n            member['actual_amount'] = take['actual_amount']\n            member['last_week'] = last_week.get(m_id, D_ZERO)\n            x = self.compute_max_this_week(m_id, last_week)\n            member['max_this_week'] = x if x.is_finite() else None\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"}]}