Skip to content
This repository has been archived by the owner on Feb 8, 2018. It is now read-only.

Commit

Permalink
shelve work on memberships; needs changes upstream
Browse files Browse the repository at this point in the history
  • Loading branch information
chadwhitacre committed May 10, 2016
1 parent 910e703 commit 08e9037
Show file tree
Hide file tree
Showing 7 changed files with 222 additions and 35 deletions.
5 changes: 4 additions & 1 deletion gratipay/models/participant/mixins/identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,12 +201,13 @@ def set_identity_verification(self, country_id, is_verified):
""", dict(locals(), participant_id=self.id))

cursor.run("""
has_verified_identity = cursor.one("""
UPDATE participant_identities
SET is_verified=%(is_verified)s
WHERE participant_id=%(participant_id)s
AND country_id=%(country_id)s
RETURNING (SELECT has_verified_identity FROM participants WHERE id=%(participant_id)s)
""", dict(locals(), participant_id=self.id))

Expand All @@ -218,6 +219,8 @@ def set_identity_verification(self, country_id, is_verified):
, action=action + ' identity'
)

import pdb; pdb.set_trace()
self.set_attributes(has_verified_identity=has_verified_identity)
add_event(cursor, 'participant', payload)


Expand Down
2 changes: 1 addition & 1 deletion gratipay/models/team/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def slugize(name):
return slug


class Team(Model, mixins.Takes):
class Team(Model, mixins.Membership):
"""Represent a Gratipay team.
"""

Expand Down
4 changes: 2 additions & 2 deletions gratipay/models/team/mixins/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .takes import TakesMixin as Takes
from .membership import MembershipMixin as Membership

__all__ = ['Takes']
__all__ = ['Membership']
148 changes: 148 additions & 0 deletions gratipay/models/team/mixins/membership.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
from __future__ import absolute_import, division, print_function, unicode_literals

from gratipay.models import add_event


class NoRoom(Exception):
pass


class MembershipMixin(object):
"""This mixin provides membership management for
:py:class:`~gratipay.models.team.Team` objects.
"""

@property
def nmembers(self):
return self.ndistributing_to


def set_takes_enabled(self, to):
"""Update whether takes are enabled for this team.
:param bool to: whether the team should have takes enabled
"""
if type(to) is not bool:
raise TypeError('Boolean required.')
with self.db.get_cursor() as cursor:
cursor.run("UPDATE teams SET takes_enabled=%s WHERE id=%s", (to, self.id))
add_event( cursor
, 'team'
, dict( action='set takes_enabled'
, id=self.id
, old_value=self.takes_enabled
, new_value=to
)
)


def get_memberships(self, cursor=None):
"""Return a list of memberships for this team.
"""
return (cursor or self.db).all("""
SELECT *
, (SELECT p.*::participants
FROM participants p
WHERE id=participant_id) AS participant
FROM current_memberships
WHERE id=%s
""", (self.id,))


def update_membership(self, participant, ntakes, recorder=None):
"""Set the number of takes for a given participant.
:param Participant participant: the participant to set the number of takes for
:param int ntakes: the number of takes
:return: the number of takes actually assigned
Each Team has 1,000 takes in total. This method will claim _up to_
``ntakes`` takes for the given participants.
"""
recorder = recorder or participant
with self.db.get_cursor() as cursor:
memberships = self.get_memberships(cursor)

nremaining = 1000
nclaimed = 0
nmembers = 0
membership = None
for _membership in memberships:
nremaining -= _membership.ntakes
nclaimed += _membership.ntakes
nmembers += 1
if membership.participant_id == participant.id:
membership = _membership
break

# sanity checks ...
assert nremaining == self.ntakes_remaining, (nremaining, self.ntakes_remaining)
assert nclaimed == self.ntakes_claimed, (nclaimed, self.ntakes_claimed)
assert nmembers == self.nmembers, (nmembers, self.nmembers)
assert participant.is_claimed, participant.id
assert participant.has_verified_identity, participant.id
assert participant.email_address, participant.id
assert not participant.is_suspicious, participant.id

if membership:
nclaimed -= membership.ntakes
nremaining += membership.ntakes
ntakes = min(ntakes, nremaining)
nremaining -= ntakes
nclaimed += ntakes

cursor.run("""
UPDATE teams
SET ntakes_claimed=%s
, ntakes_remaining=%s
, nmembers=%s
WHERE id=%s
""", (nclaimed, nremaining, nmembers, self.id))

cursor.run( """
INSERT INTO memberships
(ctime, participant_id, team_id, ntakes, recorder_id)
VALUES ( COALESCE (( SELECT ctime
FROM memberships
WHERE (participant_id=%(participant_id)s
AND team_id=%(team_id)s)
LIMIT 1
), CURRENT_TIMESTAMP)
, %(participant_id)s, %(team_id)s, %(ntakes)s, %(recorder_id)s
)
""", { 'participant_id': participant.id
, 'team_id': self.id
, 'ntakes': ntakes
, 'recorder_id': recorder.id
})

self.set_attributes(ntakes_claimed=nclaimed, ntakes_remaining=nremaining)

return ntakes


def add_member(self, participant):
"""Add a participant to this team.
:param Participant participant: the participant to add
"""
ntakes = self.update_membership(participant, 1)
if ntakes == 0:
raise NoRoom


def remove_member(self, participant):
"""Remove a participant from this team.
:param Participant participant: the participant to remove
"""
self.update_membership(participant, 0)
28 changes: 0 additions & 28 deletions gratipay/models/team/mixins/takes.py

This file was deleted.

41 changes: 41 additions & 0 deletions sql/branch.sql
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,44 @@ CREATE TRIGGER propagate_is_verified_removal
-- https://github.com/gratipay/gratipay.com/pull/4017

ALTER TABLE teams ADD COLUMN takes_enabled bool NOT NULL DEFAULT false;


-- https://github.com/gratipay/gratipay.com/pull/4018

BEGIN;

-- Farewell, old takes table!
DROP VIEW current_takes;
DROP TABLE takes;

-- Be gone, payroll! I never knew you.
DROP VIEW current_payroll;
DROP TABLE payroll;

-- memberships - which participants are part of which teams
CREATE TABLE memberships
( id bigserial PRIMARY KEY
, ctime timestamp with time zone NOT NULL
, mtime timestamp with time zone NOT NULL DEFAULT now()
, participant_id bigint NOT NULL REFERENCES participants(id)
, team_id bigint NOT NULL REFERENCES teams(id)
, ntakes int NOT NULL
, recorder_id bigint NOT NULL REFERENCES participants(id)
, CONSTRAINT not_negative CHECK (ntakes >= 0)
);

CREATE VIEW current_memberships AS
SELECT * FROM (
SELECT DISTINCT ON (participant_id, team_id) m.*
FROM memberships m
JOIN participants p ON p.id = m.participant_id
WHERE p.is_suspicious IS NOT TRUE
ORDER BY participant_id
, team_id
, mtime DESC
) AS anon WHERE ntakes > 0;

ALTER TABLE teams ADD COLUMN ntakes_claimed int default 0;
ALTER TABLE teams ADD COLUMN ntakes_remaining int default 1000;

END;
29 changes: 26 additions & 3 deletions tests/py/test_team_takes.py → tests/py/test_team_membership.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,17 @@ class Tests(Harness):

def setUp(self):
self.enterprise = self.make_team('The Enterprise')
self.crusher = self.make_participant( 'crusher'
, email_address='[email protected]'
, claimed_time='now'
)
TT = self.db.one("SELECT id FROM countries WHERE code2='TT'")
self.crusher.store_identity_info(TT, 'nothing-enforced', {'name': 'Crusher'})
self.crusher.set_identity_verification(TT, True)


def test_team_object_subclasses_takes_mixin(self):
assert isinstance(self.enterprise, mixins.Takes)
assert isinstance(self.enterprise, mixins.Membership)

def test_takes_enabled_defaults_to_false(self):
assert self.enterprise.takes_enabled is False
Expand All @@ -35,10 +42,26 @@ def test_ste_sets_takes_rejects_non_boolean_values(self):

def test_ste_setting_takes_enabled_is_a_logged_event(self):
self.enterprise.set_takes_enabled(True)
events = self.db.all("SELECT * FROM events")
assert len(events) == 1
events = self.db.all("SELECT * FROM events ORDER BY ts DESC")
assert events[0].type == 'team'
assert events[0].payload['action'] == 'set takes_enabled'
assert events[0].payload['old_value'] == False
assert events[0].payload['new_value'] == True
assert events[0].payload['id'] == self.enterprise.id


# gm - get_memberships

def test_gm_returns_an_empty_list_when_there_are_no_members(self):
assert self.enterprise.get_memberships() == []


# um - update_memberhip

def test_um_updates_membership(self):
assert self.enterprise.update_membership(self.crusher, 537) == 537

def test_um_actually_updates_membership(self):
self.enterprise.update_membership(self.crusher, 537)
memberships = self.enterprise.get_memberships()
assert [(m.participant.username, m.ntakes) for m in memberships] == [('crusher', 537)]

0 comments on commit 08e9037

Please sign in to comment.