We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
fill out the test suite for the mixins, prune old tests
Reticketed from #3994.


--- 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/mixins/membership.py`
Content:
```
1 from __future__ import absolute_import, division, print_function, unicode_literals
2 
3 from .takes import ZERO, PENNY
4 
5 
6 class MembershipMixin(object):
7     """Teams may have zero or more members, who are participants that take money from the team.
8     """
9 
10     def add_member(self, participant, recorder):
11         """Add a participant to this team.
12 
13         :param Participant participant: the participant to add
14         :param Participant recorder: the participant making the change
15 
16         """
17         self.set_take_for(participant, PENNY, recorder)
18 
19 
20     def remove_member(self, participant, recorder):
21         """Remove a participant from this team.
22 
23         :param Participant participant: the participant to remove
24         :param Participant recorder: the participant making the change
25 
26         """
27         self.set_take_for(participant, ZERO, recorder)
28 
29 
30     def remove_all_members(self, cursor=None):
31         (cursor or self.db).run("""
32             INSERT INTO takes (ctime, member, team, amount, recorder) (
33                 SELECT ctime, member, %(username)s, 0.00, %(username)s
34                   FROM current_takes
35                  WHERE team=%(username)s
36                    AND amount > 0
37             );
38         """, dict(username=self.username))
39 
40 
41     @property
42     def nmembers(self):
43         """The number of members. Read-only and computed (not in the db); equal to
44         :py:attr:`~gratipay.models.team.mixins.takes.ndistributing_to`.
45         """
46         return self.ndistributing_to
47 
48 
49     def get_memberships(self, current_participant=None):
50         """Return a list of member dicts.
51         """
52         takes = self.compute_actual_takes()
53         members = []
54         for take in takes.values():
55             member = {}
56             member['participant_id'] = take['participant'].id
57             member['username'] = take['participant'].username
58             member['take'] = take['nominal_amount']
59             member['balance'] = take['balance']
60             member['percentage'] = take['percentage']
61 
62             member['editing_allowed'] = False
63             member['is_current_user'] = False
64             if current_participant:
65                 member['removal_allowed'] = current_participant.username == self.owner
66                 if member['username'] == current_participant.username:
67                     member['is_current_user'] = True
68                     if take['ctime'] is not None:
69                         # current user, but not the team itself
70                         member['editing_allowed']= True
71 
72             member['last_week'] = self.get_take_last_week_for(member['participant_id'])
73             members.append(member)
74         return members
75
```

Path: `gratipay/models/team/mixins/takes.py`
Content:
```
1 from __future__ import absolute_import, division, print_function, unicode_literals
2 
3 from collections import OrderedDict
4 from decimal import Decimal as D
5 
6 ZERO = D('0.00')
7 PENNY = D('0.01')
8 
9 
10 class TakesMixin(object):
11     """:py:class:`~gratipay.models.participant.Participant` s who are members
12     of a :py:class:`~gratipay.models.team.Team` may take money from the team
13     during :py:class:`~gratipay.billing.payday.Payday`. Only the team owner may
14     add a new member, by setting their take to a penny, but team owners may
15     *only* set their take to a penny---no more. Team owners may also remove
16     members, by setting their take to zero, as may the members themselves, who
17     may also set their take to whatever they wish.
18     """
19 
20     #: The total amount of money the team distributes to participants
21     #: (including the owner) during payday. Read-only; equal to
22     #: :py:attr:`~gratipay.models.team.Team.receiving`.
23 
24     distributing = 0
25 
26 
27     #: The number of participants (including the owner) that the team
28     #: distributes money to during payday. Read-only; modified by
29     #: :py:meth:`set_take_for`.
30 
31     ndistributing_to = 0
32 
33 
34     def get_take_last_week_for(self, participant_id):
35         """Get the participant's nominal take last week.
36         """
37         return self.db.one("""
38 
39             SELECT amount
40               FROM takes
41              WHERE team_id=%s AND participant_id=%s
42                AND mtime < (
43                        SELECT ts_start
44                          FROM paydays
45                         WHERE ts_end > ts_start
46                      ORDER BY ts_start DESC LIMIT 1
47                    )
48           ORDER BY mtime DESC LIMIT 1
49 
50         """, (self.id, participant_id), default=ZERO)
51 
52 
53     def set_take_for(self, participant, take, recorder, cursor=None):
54         """Set the amount a participant wants to take from this team during payday.
55 
56         :param Participant participant: the participant to set the take for
57         :param Decimal take: the amount the participant wants to take
58         :param Participant recorder: the participant making the change
59 
60         :return: the new take as a py:class:`~decimal.Decimal`
61         :raises: :py:exc:`NotAllowed`
62 
63         It is a bug to pass in a ``participant`` or ``recorder`` that is
64         suspicious, unclaimed, or without a verified email and identity.
65         Furthermore, :py:exc:`NotAllowed` is raised in the following circumstances:
66 
67         - ``recorder`` is neither ``participant`` nor the team owner
68         - ``recorder`` is the team owner and ``take`` is neither zero nor $0.01
69         - ``recorder`` is ``participant``, but ``participant`` isn't already on the team
70 
71         """
72         def vet(p):
73             if p.is_suspicious:
74                 raise NotAllowed("user must not be flagged as suspicious")
75             elif not p.has_verified_identity:
76                 raise NotAllowed("user must have a verified identity")
77             elif not p.email_address:
78                 raise NotAllowed("user must have added at least one email address")
79             elif not p.is_claimed:
80                 raise NotAllowed("user must have claimed the account")
81 
82         vet(participant)
83         vet(recorder)
84 
85         owner_recording = recorder.username == self.owner
86         owner_taking = participant.username == self.owner
87         taker_recording = recorder == participant
88         adding_or_removing = take in (ZERO, PENNY)
89 
90         if owner_recording:
91             if not adding_or_removing and not owner_taking:
92                 raise NotAllowed("owner can only add and remove members, not otherwise set takes")
93         elif not taker_recording:
94             raise NotAllowed("can only set own take")
95 
96         with self.db.get_cursor(cursor) as cursor:
97             cursor.run("LOCK TABLE takes IN EXCLUSIVE MODE")  # avoid race conditions
98 
99             # Compute the current takes
100             old_takes = self.compute_actual_takes(cursor)
101 
102             if recorder.username != self.owner:
103                 if recorder == participant and participant.id not in old_takes:
104                     raise NotAllowed("can only set take if already a member of the team")
105 
106             new_take = cursor.one( """
107 
108                 INSERT INTO takes
109                             (ctime, participant_id, team_id, amount, recorder_id)
110                      VALUES ( COALESCE (( SELECT ctime
111                                             FROM takes
112                                            WHERE (participant_id=%(participant_id)s
113                                                   AND team_id=%(team_id)s)
114                                            LIMIT 1
115                                          ), CURRENT_TIMESTAMP)
116                             , %(participant_id)s, %(team_id)s, %(amount)s, %(recorder_id)s
117                              )
118                   RETURNING amount
119 
120             """, { 'participant_id': participant.id
121                  , 'team_id': self.id
122                  , 'amount': take
123                  , 'recorder_id': recorder.id
124                   })
125 
126             # Compute the new takes
127             all_new_takes = self.compute_actual_takes(cursor)
128 
129             # Update computed values
130             self.update_taking(old_takes, all_new_takes, cursor, participant)
131             self.update_distributing(all_new_takes, cursor)
132 
133             return new_take
134 
135 
136     def get_take_for(self, participant, cursor=None):
137         """
138         :param Participant participant: the participant to get the take for
139         :param GratipayDB cursor: a database cursor; if ``None``, a new cursor will be used
140         :return: a :py:class:`~decimal.Decimal`: the ``participant``'s take from this team, or 0.
141         """
142         return (cursor or self.db).one("""
143 
144             SELECT amount
145               FROM current_takes
146              WHERE team_id=%s AND participant_id=%s
147 
148         """, (self.id, participant.id), default=ZERO)
149 
150 
151     def update_taking(self, old_takes, new_takes, cursor=None, member=None):
152         """Update `taking` amounts based on the difference between `old_takes`
153         and `new_takes`.
154         """
155 
156         # XXX Deal with owner as well as members
157 
158         for participant_id in set(old_takes.keys()).union(new_takes.keys()):
159             old = old_takes.get(participant_id, {}).get('actual_amount', ZERO)
160             new = new_takes.get(participant_id, {}).get('actual_amount', ZERO)
161             delta = new - old
162             if delta != 0:
163                 taking = (cursor or self.db).one("""
164                     UPDATE participants
165                        SET taking = (taking + %(delta)s)
166                      WHERE id=%(participant_id)s
167                  RETURNING taking
168                 """, dict(participant_id=participant_id, delta=delta))
169                 if member and participant_id == member.id:
170                     member.set_attributes(taking=taking)
171 
172 
173     def update_distributing(self, new_takes, cursor=None):
174         """Update the computed values on the team.
175         """
176         distributing = sum(t['actual_amount'] for t in new_takes.values())
177         ndistributing_to = len(tuple(t for t in new_takes.values() if t['actual_amount'] > 0))
178 
179         r = (cursor or self.db).one("""
180                UPDATE teams
181                   SET distributing=%s, ndistributing_to=%s WHERE id=%s
182             RETURNING distributing, ndistributing_to
183         """, (distributing, ndistributing_to, self.id))
184 
185         self.set_attributes(**r._asdict())
186 
187 
188     def get_current_takes(self, cursor=None):
189         """Return a list of member takes for a team.
190         """
191         TAKES = """
192             SELECT p.*::participants AS participant
193                  , ct.amount, ct.ctime, ct.mtime
194               FROM current_takes ct
195               JOIN participants p
196                 ON ct.participant_id = p.id
197              WHERE team_id=%(team_id)s
198           ORDER BY amount ASC, ctime ASC
199         """
200         records = (cursor or self.db).all(TAKES, dict(team_id=self.id))
201         return [r._asdict() for r in records]
202 
203 
204     def compute_actual_takes(self, cursor=None):
205         """Get the takes, compute the actual amounts, and return an OrderedDict.
206         """
207         actual_takes = OrderedDict()
208         nominal_takes = self.get_current_takes(cursor=cursor)
209         available = balance = self.available
210         for take in nominal_takes:
211             nominal_amount = take['nominal_amount'] = take.pop('amount')
212             actual_amount = take['actual_amount'] = min(nominal_amount, balance)
213             take['balance'] = balance = balance - actual_amount
214             take['percentage'] = actual_amount / available
215             actual_takes[take['participant'].id] = take
216         return actual_takes
217 
218 
219 class NotAllowed(Exception):
220     """Raised by :py:meth:`set_take_for` if ``recorder`` is not allowed to set
221     the take for ``participant``.
222     """
223
```
--- 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/gratipay/models/team/mixins/membership.py b/gratipay/models/team/mixins/membership.py
--- a/gratipay/models/team/mixins/membership.py
+++ b/gratipay/models/team/mixins/membership.py
@@ -27,17 +27,6 @@
         self.set_take_for(participant, ZERO, recorder)
 
 
-    def remove_all_members(self, cursor=None):
-        (cursor or self.db).run("""
-            INSERT INTO takes (ctime, member, team, amount, recorder) (
-                SELECT ctime, member, %(username)s, 0.00, %(username)s
-                  FROM current_takes
-                 WHERE team=%(username)s
-                   AND amount > 0
-            );
-        """, dict(username=self.username))
-
-
     @property
     def nmembers(self):
         """The number of members. Read-only and computed (not in the db); equal to
@@ -64,11 +53,8 @@
             if current_participant:
                 member['removal_allowed'] = current_participant.username == self.owner
                 if member['username'] == current_participant.username:
-                    member['is_current_user'] = True
-                    if take['ctime'] is not None:
-                        # current user, but not the team itself
-                        member['editing_allowed']= True
+                    member['editing_allowed']= True
 
-            member['last_week'] = self.get_take_last_week_for(member['participant_id'])
+            member['last_week'] = self.get_take_last_week_for(take['participant'])
             members.append(member)
         return members
diff --git a/gratipay/models/team/mixins/takes.py b/gratipay/models/team/mixins/takes.py
--- a/gratipay/models/team/mixins/takes.py
+++ b/gratipay/models/team/mixins/takes.py
@@ -31,25 +31,6 @@
     ndistributing_to = 0
 
 
-    def get_take_last_week_for(self, participant_id):
-        """Get the participant's nominal take last week.
-        """
-        return self.db.one("""
-
-            SELECT amount
-              FROM takes
-             WHERE team_id=%s AND participant_id=%s
-               AND mtime < (
-                       SELECT ts_start
-                         FROM paydays
-                        WHERE ts_end > ts_start
-                     ORDER BY ts_start DESC LIMIT 1
-                   )
-          ORDER BY mtime DESC LIMIT 1
-
-        """, (self.id, participant_id), default=ZERO)
-
-
     def set_take_for(self, participant, take, recorder, cursor=None):
         """Set the amount a participant wants to take from this team during payday.
 
@@ -72,10 +53,10 @@
         def vet(p):
             if p.is_suspicious:
                 raise NotAllowed("user must not be flagged as suspicious")
-            elif not p.has_verified_identity:
-                raise NotAllowed("user must have a verified identity")
             elif not p.email_address:
                 raise NotAllowed("user must have added at least one email address")
+            elif not p.has_verified_identity:
+                raise NotAllowed("user must have a verified identity")
             elif not p.is_claimed:
                 raise NotAllowed("user must have claimed the account")
 
@@ -148,6 +129,30 @@
         """, (self.id, participant.id), default=ZERO)
 
 
+    def get_take_last_week_for(self, participant, cursor=None):
+        """
+        :param Participant participant: the participant to get the take for
+        :param GratipayDB cursor: a database cursor; if ``None``, a new cursor
+            will be used
+        :return: a :py:class:`~decimal.Decimal`: the ``participant``'s take
+            from this team at the beginning of the last completed payday, or 0.
+        """
+        return (cursor or self.db).one("""
+
+            SELECT amount
+              FROM takes
+             WHERE team_id=%s AND participant_id=%s
+               AND mtime < (
+                       SELECT ts_start
+                         FROM paydays
+                        WHERE ts_end > ts_start
+                     ORDER BY ts_start DESC LIMIT 1
+                   )
+          ORDER BY mtime DESC LIMIT 1
+
+        """, (self.id, participant.id), default=ZERO)
+
+
     def update_taking(self, old_takes, new_takes, cursor=None, member=None):
         """Update `taking` amounts based on the difference between `old_takes`
         and `new_takes`.

{"golden_diff": "diff --git a/gratipay/models/team/mixins/membership.py b/gratipay/models/team/mixins/membership.py\n--- a/gratipay/models/team/mixins/membership.py\n+++ b/gratipay/models/team/mixins/membership.py\n@@ -27,17 +27,6 @@\n         self.set_take_for(participant, ZERO, recorder)\n \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, %(username)s, 0.00, %(username)s\n-                  FROM current_takes\n-                 WHERE team=%(username)s\n-                   AND amount > 0\n-            );\n-        \"\"\", dict(username=self.username))\n-\n-\n     @property\n     def nmembers(self):\n         \"\"\"The number of members. Read-only and computed (not in the db); equal to\n@@ -64,11 +53,8 @@\n             if current_participant:\n                 member['removal_allowed'] = current_participant.username == self.owner\n                 if member['username'] == current_participant.username:\n-                    member['is_current_user'] = True\n-                    if take['ctime'] is not None:\n-                        # current user, but not the team itself\n-                        member['editing_allowed']= True\n+                    member['editing_allowed']= True\n \n-            member['last_week'] = self.get_take_last_week_for(member['participant_id'])\n+            member['last_week'] = self.get_take_last_week_for(take['participant'])\n             members.append(member)\n         return members\ndiff --git a/gratipay/models/team/mixins/takes.py b/gratipay/models/team/mixins/takes.py\n--- a/gratipay/models/team/mixins/takes.py\n+++ b/gratipay/models/team/mixins/takes.py\n@@ -31,25 +31,6 @@\n     ndistributing_to = 0\n \n \n-    def get_take_last_week_for(self, participant_id):\n-        \"\"\"Get the participant's nominal take last week.\n-        \"\"\"\n-        return self.db.one(\"\"\"\n-\n-            SELECT amount\n-              FROM takes\n-             WHERE team_id=%s AND participant_id=%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 mtime DESC LIMIT 1\n-\n-        \"\"\", (self.id, participant_id), default=ZERO)\n-\n-\n     def set_take_for(self, participant, take, recorder, cursor=None):\n         \"\"\"Set the amount a participant wants to take from this team during payday.\n \n@@ -72,10 +53,10 @@\n         def vet(p):\n             if p.is_suspicious:\n                 raise NotAllowed(\"user must not be flagged as suspicious\")\n-            elif not p.has_verified_identity:\n-                raise NotAllowed(\"user must have a verified identity\")\n             elif not p.email_address:\n                 raise NotAllowed(\"user must have added at least one email address\")\n+            elif not p.has_verified_identity:\n+                raise NotAllowed(\"user must have a verified identity\")\n             elif not p.is_claimed:\n                 raise NotAllowed(\"user must have claimed the account\")\n \n@@ -148,6 +129,30 @@\n         \"\"\", (self.id, participant.id), default=ZERO)\n \n \n+    def get_take_last_week_for(self, participant, cursor=None):\n+        \"\"\"\n+        :param Participant participant: the participant to get the take for\n+        :param GratipayDB cursor: a database cursor; if ``None``, a new cursor\n+            will be used\n+        :return: a :py:class:`~decimal.Decimal`: the ``participant``'s take\n+            from this team at the beginning of the last completed payday, or 0.\n+        \"\"\"\n+        return (cursor or self.db).one(\"\"\"\n+\n+            SELECT amount\n+              FROM takes\n+             WHERE team_id=%s AND participant_id=%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 mtime DESC LIMIT 1\n+\n+        \"\"\", (self.id, participant.id), default=ZERO)\n+\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", "issue": "fill out the test suite for the mixins, prune old tests\nReticketed from #3994.\n\n", "before_files": [{"content": "from __future__ import absolute_import, division, print_function, unicode_literals\n\nfrom .takes import ZERO, PENNY\n\n\nclass MembershipMixin(object):\n    \"\"\"Teams may have zero or more members, who are participants that take money from the team.\n    \"\"\"\n\n    def add_member(self, participant, recorder):\n        \"\"\"Add a participant to this team.\n\n        :param Participant participant: the participant to add\n        :param Participant recorder: the participant making the change\n\n        \"\"\"\n        self.set_take_for(participant, PENNY, recorder)\n\n\n    def remove_member(self, participant, recorder):\n        \"\"\"Remove a participant from this team.\n\n        :param Participant participant: the participant to remove\n        :param Participant recorder: the participant making the change\n\n        \"\"\"\n        self.set_take_for(participant, ZERO, recorder)\n\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, %(username)s, 0.00, %(username)s\n                  FROM current_takes\n                 WHERE team=%(username)s\n                   AND amount > 0\n            );\n        \"\"\", dict(username=self.username))\n\n\n    @property\n    def nmembers(self):\n        \"\"\"The number of members. Read-only and computed (not in the db); equal to\n        :py:attr:`~gratipay.models.team.mixins.takes.ndistributing_to`.\n        \"\"\"\n        return self.ndistributing_to\n\n\n    def get_memberships(self, current_participant=None):\n        \"\"\"Return a list of member dicts.\n        \"\"\"\n        takes = self.compute_actual_takes()\n        members = []\n        for take in takes.values():\n            member = {}\n            member['participant_id'] = take['participant'].id\n            member['username'] = take['participant'].username\n            member['take'] = take['nominal_amount']\n            member['balance'] = take['balance']\n            member['percentage'] = take['percentage']\n\n            member['editing_allowed'] = False\n            member['is_current_user'] = False\n            if current_participant:\n                member['removal_allowed'] = current_participant.username == self.owner\n                if member['username'] == current_participant.username:\n                    member['is_current_user'] = True\n                    if take['ctime'] is not None:\n                        # current user, but not the team itself\n                        member['editing_allowed']= True\n\n            member['last_week'] = self.get_take_last_week_for(member['participant_id'])\n            members.append(member)\n        return members\n", "path": "gratipay/models/team/mixins/membership.py"}, {"content": "from __future__ import absolute_import, division, print_function, unicode_literals\n\nfrom collections import OrderedDict\nfrom decimal import Decimal as D\n\nZERO = D('0.00')\nPENNY = D('0.01')\n\n\nclass TakesMixin(object):\n    \"\"\":py:class:`~gratipay.models.participant.Participant` s who are members\n    of a :py:class:`~gratipay.models.team.Team` may take money from the team\n    during :py:class:`~gratipay.billing.payday.Payday`. Only the team owner may\n    add a new member, by setting their take to a penny, but team owners may\n    *only* set their take to a penny---no more. Team owners may also remove\n    members, by setting their take to zero, as may the members themselves, who\n    may also set their take to whatever they wish.\n    \"\"\"\n\n    #: The total amount of money the team distributes to participants\n    #: (including the owner) during payday. Read-only; equal to\n    #: :py:attr:`~gratipay.models.team.Team.receiving`.\n\n    distributing = 0\n\n\n    #: The number of participants (including the owner) that the team\n    #: distributes money to during payday. Read-only; modified by\n    #: :py:meth:`set_take_for`.\n\n    ndistributing_to = 0\n\n\n    def get_take_last_week_for(self, participant_id):\n        \"\"\"Get the participant's nominal take last week.\n        \"\"\"\n        return self.db.one(\"\"\"\n\n            SELECT amount\n              FROM takes\n             WHERE team_id=%s AND participant_id=%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 mtime DESC LIMIT 1\n\n        \"\"\", (self.id, participant_id), default=ZERO)\n\n\n    def set_take_for(self, participant, take, recorder, cursor=None):\n        \"\"\"Set the amount a participant wants to take from this team during payday.\n\n        :param Participant participant: the participant to set the take for\n        :param Decimal take: the amount the participant wants to take\n        :param Participant recorder: the participant making the change\n\n        :return: the new take as a py:class:`~decimal.Decimal`\n        :raises: :py:exc:`NotAllowed`\n\n        It is a bug to pass in a ``participant`` or ``recorder`` that is\n        suspicious, unclaimed, or without a verified email and identity.\n        Furthermore, :py:exc:`NotAllowed` is raised in the following circumstances:\n\n        - ``recorder`` is neither ``participant`` nor the team owner\n        - ``recorder`` is the team owner and ``take`` is neither zero nor $0.01\n        - ``recorder`` is ``participant``, but ``participant`` isn't already on the team\n\n        \"\"\"\n        def vet(p):\n            if p.is_suspicious:\n                raise NotAllowed(\"user must not be flagged as suspicious\")\n            elif not p.has_verified_identity:\n                raise NotAllowed(\"user must have a verified identity\")\n            elif not p.email_address:\n                raise NotAllowed(\"user must have added at least one email address\")\n            elif not p.is_claimed:\n                raise NotAllowed(\"user must have claimed the account\")\n\n        vet(participant)\n        vet(recorder)\n\n        owner_recording = recorder.username == self.owner\n        owner_taking = participant.username == self.owner\n        taker_recording = recorder == participant\n        adding_or_removing = take in (ZERO, PENNY)\n\n        if owner_recording:\n            if not adding_or_removing and not owner_taking:\n                raise NotAllowed(\"owner can only add and remove members, not otherwise set takes\")\n        elif not taker_recording:\n            raise NotAllowed(\"can only set own take\")\n\n        with self.db.get_cursor(cursor) as cursor:\n            cursor.run(\"LOCK TABLE takes IN EXCLUSIVE MODE\")  # avoid race conditions\n\n            # Compute the current takes\n            old_takes = self.compute_actual_takes(cursor)\n\n            if recorder.username != self.owner:\n                if recorder == participant and participant.id not in old_takes:\n                    raise NotAllowed(\"can only set take if already a member of the team\")\n\n            new_take = cursor.one( \"\"\"\n\n                INSERT INTO takes\n                            (ctime, participant_id, team_id, amount, recorder_id)\n                     VALUES ( COALESCE (( SELECT ctime\n                                            FROM takes\n                                           WHERE (participant_id=%(participant_id)s\n                                                  AND team_id=%(team_id)s)\n                                           LIMIT 1\n                                         ), CURRENT_TIMESTAMP)\n                            , %(participant_id)s, %(team_id)s, %(amount)s, %(recorder_id)s\n                             )\n                  RETURNING amount\n\n            \"\"\", { 'participant_id': participant.id\n                 , 'team_id': self.id\n                 , 'amount': take\n                 , 'recorder_id': recorder.id\n                  })\n\n            # Compute the new takes\n            all_new_takes = self.compute_actual_takes(cursor)\n\n            # Update computed values\n            self.update_taking(old_takes, all_new_takes, cursor, participant)\n            self.update_distributing(all_new_takes, cursor)\n\n            return new_take\n\n\n    def get_take_for(self, participant, cursor=None):\n        \"\"\"\n        :param Participant participant: the participant to get the take for\n        :param GratipayDB cursor: a database cursor; if ``None``, a new cursor will be used\n        :return: a :py:class:`~decimal.Decimal`: the ``participant``'s take from this team, or 0.\n        \"\"\"\n        return (cursor or self.db).one(\"\"\"\n\n            SELECT amount\n              FROM current_takes\n             WHERE team_id=%s AND participant_id=%s\n\n        \"\"\", (self.id, participant.id), default=ZERO)\n\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\n        # XXX Deal with owner as well as members\n\n        for participant_id in set(old_takes.keys()).union(new_takes.keys()):\n            old = old_takes.get(participant_id, {}).get('actual_amount', ZERO)\n            new = new_takes.get(participant_id, {}).get('actual_amount', ZERO)\n            delta = new - old\n            if delta != 0:\n                taking = (cursor or self.db).one(\"\"\"\n                    UPDATE participants\n                       SET taking = (taking + %(delta)s)\n                     WHERE id=%(participant_id)s\n                 RETURNING taking\n                \"\"\", dict(participant_id=participant_id, delta=delta))\n                if member and participant_id == member.id:\n                    member.set_attributes(taking=taking)\n\n\n    def update_distributing(self, new_takes, cursor=None):\n        \"\"\"Update the computed values on the team.\n        \"\"\"\n        distributing = sum(t['actual_amount'] for t in new_takes.values())\n        ndistributing_to = len(tuple(t for t in new_takes.values() if t['actual_amount'] > 0))\n\n        r = (cursor or self.db).one(\"\"\"\n               UPDATE teams\n                  SET distributing=%s, ndistributing_to=%s WHERE id=%s\n            RETURNING distributing, ndistributing_to\n        \"\"\", (distributing, ndistributing_to, self.id))\n\n        self.set_attributes(**r._asdict())\n\n\n    def get_current_takes(self, cursor=None):\n        \"\"\"Return a list of member takes for a team.\n        \"\"\"\n        TAKES = \"\"\"\n            SELECT p.*::participants AS participant\n                 , ct.amount, ct.ctime, ct.mtime\n              FROM current_takes ct\n              JOIN participants p\n                ON ct.participant_id = p.id\n             WHERE team_id=%(team_id)s\n          ORDER BY amount ASC, ctime ASC\n        \"\"\"\n        records = (cursor or self.db).all(TAKES, dict(team_id=self.id))\n        return [r._asdict() for r in records]\n\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        available = balance = self.available\n        for take in nominal_takes:\n            nominal_amount = take['nominal_amount'] = take.pop('amount')\n            actual_amount = take['actual_amount'] = min(nominal_amount, balance)\n            take['balance'] = balance = balance - actual_amount\n            take['percentage'] = actual_amount / available\n            actual_takes[take['participant'].id] = take\n        return actual_takes\n\n\nclass NotAllowed(Exception):\n    \"\"\"Raised by :py:meth:`set_take_for` if ``recorder`` is not allowed to set\n    the take for ``participant``.\n    \"\"\"\n", "path": "gratipay/models/team/mixins/takes.py"}], "after_files": [{"content": "from __future__ import absolute_import, division, print_function, unicode_literals\n\nfrom .takes import ZERO, PENNY\n\n\nclass MembershipMixin(object):\n    \"\"\"Teams may have zero or more members, who are participants that take money from the team.\n    \"\"\"\n\n    def add_member(self, participant, recorder):\n        \"\"\"Add a participant to this team.\n\n        :param Participant participant: the participant to add\n        :param Participant recorder: the participant making the change\n\n        \"\"\"\n        self.set_take_for(participant, PENNY, recorder)\n\n\n    def remove_member(self, participant, recorder):\n        \"\"\"Remove a participant from this team.\n\n        :param Participant participant: the participant to remove\n        :param Participant recorder: the participant making the change\n\n        \"\"\"\n        self.set_take_for(participant, ZERO, recorder)\n\n\n    @property\n    def nmembers(self):\n        \"\"\"The number of members. Read-only and computed (not in the db); equal to\n        :py:attr:`~gratipay.models.team.mixins.takes.ndistributing_to`.\n        \"\"\"\n        return self.ndistributing_to\n\n\n    def get_memberships(self, current_participant=None):\n        \"\"\"Return a list of member dicts.\n        \"\"\"\n        takes = self.compute_actual_takes()\n        members = []\n        for take in takes.values():\n            member = {}\n            member['participant_id'] = take['participant'].id\n            member['username'] = take['participant'].username\n            member['take'] = take['nominal_amount']\n            member['balance'] = take['balance']\n            member['percentage'] = take['percentage']\n\n            member['editing_allowed'] = False\n            member['is_current_user'] = False\n            if current_participant:\n                member['removal_allowed'] = current_participant.username == self.owner\n                if member['username'] == current_participant.username:\n                    member['editing_allowed']= True\n\n            member['last_week'] = self.get_take_last_week_for(take['participant'])\n            members.append(member)\n        return members\n", "path": "gratipay/models/team/mixins/membership.py"}, {"content": "from __future__ import absolute_import, division, print_function, unicode_literals\n\nfrom collections import OrderedDict\nfrom decimal import Decimal as D\n\nZERO = D('0.00')\nPENNY = D('0.01')\n\n\nclass TakesMixin(object):\n    \"\"\":py:class:`~gratipay.models.participant.Participant` s who are members\n    of a :py:class:`~gratipay.models.team.Team` may take money from the team\n    during :py:class:`~gratipay.billing.payday.Payday`. Only the team owner may\n    add a new member, by setting their take to a penny, but team owners may\n    *only* set their take to a penny---no more. Team owners may also remove\n    members, by setting their take to zero, as may the members themselves, who\n    may also set their take to whatever they wish.\n    \"\"\"\n\n    #: The total amount of money the team distributes to participants\n    #: (including the owner) during payday. Read-only; equal to\n    #: :py:attr:`~gratipay.models.team.Team.receiving`.\n\n    distributing = 0\n\n\n    #: The number of participants (including the owner) that the team\n    #: distributes money to during payday. Read-only; modified by\n    #: :py:meth:`set_take_for`.\n\n    ndistributing_to = 0\n\n\n    def set_take_for(self, participant, take, recorder, cursor=None):\n        \"\"\"Set the amount a participant wants to take from this team during payday.\n\n        :param Participant participant: the participant to set the take for\n        :param Decimal take: the amount the participant wants to take\n        :param Participant recorder: the participant making the change\n\n        :return: the new take as a py:class:`~decimal.Decimal`\n        :raises: :py:exc:`NotAllowed`\n\n        It is a bug to pass in a ``participant`` or ``recorder`` that is\n        suspicious, unclaimed, or without a verified email and identity.\n        Furthermore, :py:exc:`NotAllowed` is raised in the following circumstances:\n\n        - ``recorder`` is neither ``participant`` nor the team owner\n        - ``recorder`` is the team owner and ``take`` is neither zero nor $0.01\n        - ``recorder`` is ``participant``, but ``participant`` isn't already on the team\n\n        \"\"\"\n        def vet(p):\n            if p.is_suspicious:\n                raise NotAllowed(\"user must not be flagged as suspicious\")\n            elif not p.email_address:\n                raise NotAllowed(\"user must have added at least one email address\")\n            elif not p.has_verified_identity:\n                raise NotAllowed(\"user must have a verified identity\")\n            elif not p.is_claimed:\n                raise NotAllowed(\"user must have claimed the account\")\n\n        vet(participant)\n        vet(recorder)\n\n        owner_recording = recorder.username == self.owner\n        owner_taking = participant.username == self.owner\n        taker_recording = recorder == participant\n        adding_or_removing = take in (ZERO, PENNY)\n\n        if owner_recording:\n            if not adding_or_removing and not owner_taking:\n                raise NotAllowed(\"owner can only add and remove members, not otherwise set takes\")\n        elif not taker_recording:\n            raise NotAllowed(\"can only set own take\")\n\n        with self.db.get_cursor(cursor) as cursor:\n            cursor.run(\"LOCK TABLE takes IN EXCLUSIVE MODE\")  # avoid race conditions\n\n            # Compute the current takes\n            old_takes = self.compute_actual_takes(cursor)\n\n            if recorder.username != self.owner:\n                if recorder == participant and participant.id not in old_takes:\n                    raise NotAllowed(\"can only set take if already a member of the team\")\n\n            new_take = cursor.one( \"\"\"\n\n                INSERT INTO takes\n                            (ctime, participant_id, team_id, amount, recorder_id)\n                     VALUES ( COALESCE (( SELECT ctime\n                                            FROM takes\n                                           WHERE (participant_id=%(participant_id)s\n                                                  AND team_id=%(team_id)s)\n                                           LIMIT 1\n                                         ), CURRENT_TIMESTAMP)\n                            , %(participant_id)s, %(team_id)s, %(amount)s, %(recorder_id)s\n                             )\n                  RETURNING amount\n\n            \"\"\", { 'participant_id': participant.id\n                 , 'team_id': self.id\n                 , 'amount': take\n                 , 'recorder_id': recorder.id\n                  })\n\n            # Compute the new takes\n            all_new_takes = self.compute_actual_takes(cursor)\n\n            # Update computed values\n            self.update_taking(old_takes, all_new_takes, cursor, participant)\n            self.update_distributing(all_new_takes, cursor)\n\n            return new_take\n\n\n    def get_take_for(self, participant, cursor=None):\n        \"\"\"\n        :param Participant participant: the participant to get the take for\n        :param GratipayDB cursor: a database cursor; if ``None``, a new cursor will be used\n        :return: a :py:class:`~decimal.Decimal`: the ``participant``'s take from this team, or 0.\n        \"\"\"\n        return (cursor or self.db).one(\"\"\"\n\n            SELECT amount\n              FROM current_takes\n             WHERE team_id=%s AND participant_id=%s\n\n        \"\"\", (self.id, participant.id), default=ZERO)\n\n\n    def get_take_last_week_for(self, participant, cursor=None):\n        \"\"\"\n        :param Participant participant: the participant to get the take for\n        :param GratipayDB cursor: a database cursor; if ``None``, a new cursor\n            will be used\n        :return: a :py:class:`~decimal.Decimal`: the ``participant``'s take\n            from this team at the beginning of the last completed payday, or 0.\n        \"\"\"\n        return (cursor or self.db).one(\"\"\"\n\n            SELECT amount\n              FROM takes\n             WHERE team_id=%s AND participant_id=%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 mtime DESC LIMIT 1\n\n        \"\"\", (self.id, participant.id), default=ZERO)\n\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\n        # XXX Deal with owner as well as members\n\n        for participant_id in set(old_takes.keys()).union(new_takes.keys()):\n            old = old_takes.get(participant_id, {}).get('actual_amount', ZERO)\n            new = new_takes.get(participant_id, {}).get('actual_amount', ZERO)\n            delta = new - old\n            if delta != 0:\n                taking = (cursor or self.db).one(\"\"\"\n                    UPDATE participants\n                       SET taking = (taking + %(delta)s)\n                     WHERE id=%(participant_id)s\n                 RETURNING taking\n                \"\"\", dict(participant_id=participant_id, delta=delta))\n                if member and participant_id == member.id:\n                    member.set_attributes(taking=taking)\n\n\n    def update_distributing(self, new_takes, cursor=None):\n        \"\"\"Update the computed values on the team.\n        \"\"\"\n        distributing = sum(t['actual_amount'] for t in new_takes.values())\n        ndistributing_to = len(tuple(t for t in new_takes.values() if t['actual_amount'] > 0))\n\n        r = (cursor or self.db).one(\"\"\"\n               UPDATE teams\n                  SET distributing=%s, ndistributing_to=%s WHERE id=%s\n            RETURNING distributing, ndistributing_to\n        \"\"\", (distributing, ndistributing_to, self.id))\n\n        self.set_attributes(**r._asdict())\n\n\n    def get_current_takes(self, cursor=None):\n        \"\"\"Return a list of member takes for a team.\n        \"\"\"\n        TAKES = \"\"\"\n            SELECT p.*::participants AS participant\n                 , ct.amount, ct.ctime, ct.mtime\n              FROM current_takes ct\n              JOIN participants p\n                ON ct.participant_id = p.id\n             WHERE team_id=%(team_id)s\n          ORDER BY amount ASC, ctime ASC\n        \"\"\"\n        records = (cursor or self.db).all(TAKES, dict(team_id=self.id))\n        return [r._asdict() for r in records]\n\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        available = balance = self.available\n        for take in nominal_takes:\n            nominal_amount = take['nominal_amount'] = take.pop('amount')\n            actual_amount = take['actual_amount'] = min(nominal_amount, balance)\n            take['balance'] = balance = balance - actual_amount\n            take['percentage'] = actual_amount / available\n            actual_takes[take['participant'].id] = take\n        return actual_takes\n\n\nclass NotAllowed(Exception):\n    \"\"\"Raised by :py:meth:`set_take_for` if ``recorder`` is not allowed to set\n    the take for ``participant``.\n    \"\"\"\n", "path": "gratipay/models/team/mixins/takes.py"}]}