diff --git a/gittip/models/team.py b/gittip/models/_mixin_team.py similarity index 84% rename from gittip/models/team.py rename to gittip/models/_mixin_team.py index 6054ed5d12..5b802498b3 100644 --- a/gittip/models/team.py +++ b/gittip/models/_mixin_team.py @@ -1,11 +1,15 @@ """Teams on Gittip are plural participants with members. +""" +from decimal import Decimal +from aspen.utils import typecheck +from postgres import RealDictCursor -""" +class MemberLimitReached(Exception): pass -class Team(object): +class MixinTeam(object): """This class provides methods for working with a Participant as a Team. :param Participant participant: the underlying :py:class:`~gittip.participant.Participant` object for this team @@ -20,12 +24,12 @@ def __init__(self, participant): def show_as_team(self, user): """Return a boolean, whether to show this participant as a team. """ - if not self.participant.IS_PLURAL: + if not self.IS_PLURAL: return False if user.ADMIN: return True if not self.get_members(): - if self != user: + if self != user.participant: return False return True @@ -34,7 +38,7 @@ def add_member(self, member): """ assert self.IS_PLURAL if len(self.get_members()) == 149: - raise self.MemberLimitReached + raise MemberLimitReached self.__set_take_for(member, Decimal('0.01'), self) def remove_member(self, member): @@ -58,7 +62,7 @@ def get_take_last_week_for(self, member): assert self.IS_PLURAL membername = member.username if hasattr(member, 'username') \ else member['username'] - rec = gittip.db.one_or_zero(""" + return self.db.one_or_zero(""" SELECT amount FROM transfers @@ -67,25 +71,17 @@ def get_take_last_week_for(self, member): (SELECT ts_start FROM paydays ORDER BY ts_start DESC LIMIT 1) ORDER BY timestamp DESC LIMIT 1 - """, (self.username, membername)) - - if rec is None: - return Decimal('0.00') - else: - return rec['amount'] + """, (self.username, membername), zero=Decimal('0.00')) def get_take_for(self, member): """Return a Decimal representation of the take for this member, or 0. """ assert self.IS_PLURAL - rec = gittip.db.one_or_zero( "SELECT take FROM current_memberships " - "WHERE member=%s AND team=%s" - , (member.username, self.username) - ) - if rec is None: - return Decimal('0.00') - else: - return rec['take'] + return self.db.one_or_zero( "SELECT take FROM current_memberships " + "WHERE member=%s AND team=%s" + , (member.username, self.username) + , zero=Decimal('0.00') + ) def compute_max_this_week(self, last_week): """2x last week's take, but at least a dollar. @@ -96,7 +92,11 @@ def set_take_for(self, member, take, recorder): """Sets member's take from the team pool. """ assert self.IS_PLURAL - from gittip.models.user import User # lazy to avoid circular import + + # lazy import to avoid circular import + from gittip.security.user import User + from gittip.models.participant import Participant + typecheck( member, Participant , take, Decimal , recorder, (Participant, User) @@ -113,7 +113,7 @@ def set_take_for(self, member, take, recorder): def __set_take_for(self, member, take, recorder): assert self.IS_PLURAL # XXX Factored out for testing purposes only! :O Use .set_take_for. - gittip.db.run(""" + self.db.run(""" INSERT INTO memberships (ctime, member, team, take, recorder) VALUES ( COALESCE (( SELECT ctime @@ -133,19 +133,19 @@ def __set_take_for(self, member, take, recorder): def get_members(self): assert self.IS_PLURAL - return list(gittip.db.all(""" + return self.db.all(""" SELECT member AS username, take, ctime, mtime FROM current_memberships WHERE team=%s ORDER BY ctime DESC - """, (self.username,))) + """, (self.username,), cursor_factory=RealDictCursor) def get_teams_membership(self): assert self.IS_PLURAL TAKE = "SELECT sum(take) FROM current_memberships WHERE team=%s" - total_take = gittip.db.one_or_zero(TAKE, (self.username,))['sum'] + total_take = self.db.one_or_zero(TAKE, (self.username,))['sum'] total_take = 0 if total_take is None else total_take team_take = max(self.get_dollars_receiving() - total_take, 0) membership = { "ctime": None @@ -178,6 +178,3 @@ def get_memberships(self, current_user): member['balance'] = balance member['percentage'] = (amount / budget) if budget > 0 else 0 return members - - - diff --git a/gittip/models/community.py b/gittip/models/community.py index 292d0e96e0..4c561c1f9c 100644 --- a/gittip/models/community.py +++ b/gittip/models/community.py @@ -34,7 +34,7 @@ def get_list_for(user): :database: One SELECT, multiple rows """ - if user is None or user.ANON: + if user is None: member_test = "false" sort_order = 'DESC' params = () diff --git a/gittip/models/participant.py b/gittip/models/participant.py index caee274a30..9acfcbd903 100644 --- a/gittip/models/participant.py +++ b/gittip/models/participant.py @@ -21,6 +21,7 @@ from psycopg2 import IntegrityError from postgres.orm import Model from gittip.models._mixin_elsewhere import MixinElsewhere +from gittip.models._mixin_team import MixinTeam from gittip.utils import canonicalize @@ -31,16 +32,20 @@ NANSWERS_THRESHOLD = 0 # configured in wireup.py -class Participant(Model, MixinElsewhere): +class Participant(Model, MixinElsewhere, MixinTeam): """Represent a Gittip participant. """ typname = 'participants' def __eq__(self, other): + if not isinstance(other, Participant): + return False return self.username == other.username def __ne__(self, other): + if not isinstance(other, Participant): + return False return self.username != other.username @@ -113,17 +118,13 @@ def set_session_expires(self, expires): # Number # ====== - def is_singular(self): - rec = gittip.db.one_or_zero("SELECT number FROM participants " - "WHERE username = %s", (self.username,)) - - return rec['number'] == 'singular' - - def is_plural(self): - rec = gittip.db.one_or_zero("SELECT number FROM participants " - "WHERE username = %s", (self.username,)) + @property + def IS_SINGULAR(self): + return self.number == 'singular' - return rec['number'] == 'plural' + @property + def IS_PLURAL(self): + return self.number == 'plural' def get_teams(self): @@ -344,15 +345,13 @@ def get_tip_to(self, tippee): # XXX - def get_dollars_receiving(self): - return sum(tip.amount for tip in self.valid_tips_receiving) + Decimal('0.00') def get_dollars_receiving(self): """Return a Decimal. """ BACKED = """\ - SELECT sum(amount) AS dollars_receiving + SELECT sum(amount) FROM ( SELECT DISTINCT ON (tipper) amount , tipper @@ -366,16 +365,10 @@ def get_dollars_receiving(self): ) AS foo """ - rec = gittip.db.one_or_zero(BACKED, (self.username,)) - if rec is None: - amount = None - else: - amount = rec['dollars_receiving'] # might be None - - if amount is None: - amount = Decimal('0.00') - - return amount + return self.db.one_or_zero( BACKED + , (self.username,) + , zero=Decimal('0.00') + ) def get_dollars_giving(self): @@ -384,7 +377,7 @@ def get_dollars_giving(self): BACKED = """\ - SELECT sum(amount) AS dollars_giving + SELECT sum(amount) FROM ( SELECT DISTINCT ON (tippee) amount , tippee @@ -398,16 +391,10 @@ def get_dollars_giving(self): ) AS foo """ - rec = gittip.db.one_or_zero(BACKED, (self.username,)) - if rec is None: - amount = None - else: - amount = rec['dollars_giving'] # might be None - - if amount is None: - amount = Decimal('0.00') - - return amount + return self.db.one_or_zero( BACKED + , (self.username,) + , zero=Decimal('0.00') + ) def get_number_of_backers(self): @@ -755,7 +742,6 @@ class UsernameIsRestricted(ProblemChangingUsername): pass class UsernameAlreadyTaken(ProblemChangingUsername): pass class TooGreedy(Exception): pass -class MemberLimitReached(Exception): pass class NoSelfTipping(Exception): pass class BadAmount(Exception): pass diff --git a/gittip/utils/__init__.py b/gittip/utils/__init__.py index 08024d155b..79b198e37a 100644 --- a/gittip/utils/__init__.py +++ b/gittip/utils/__init__.py @@ -1,3 +1,4 @@ +import gittip from aspen import Response from aspen.utils import typecheck from tornado.escape import linkify @@ -275,3 +276,47 @@ def canonicalize(path, base, canonical, given): def plural(i, singular="", plural="s"): return singular if i == 1 else plural + + +def get_participant(request, restrict=True): + """Given a Request, raise Response or return Participant. + + If user is not None then we'll restrict access to owners and admins. + + """ + user = request.context['user'] + slug = request.line.uri.path['username'] + + if restrict: + if user.ANON: + request.redirect(u'/%s/' % slug) + + participant = gittip.db.one_or_zero( "SELECT participants.*::participants " + "FROM participants " + "WHERE username_lower=%s" + , (slug.lower(),) + ) + + if participant is None: + raise Response(404) + + canonicalize(request.line.uri.path.raw, '/', participant.username, slug) + + if participant.claimed_time is None: + + # This is a stub participant record for someone on another platform who + # hasn't actually registered with Gittip yet. Let's bounce the viewer + # over to the appropriate platform page. + + to = participant.resolve_unclaimed() + if to is None: + raise Response(404) + request.redirect(to) + + if restrict: + if participant != user.participant: + if not user.ADMIN: + raise Response(403) + + return participant + diff --git a/templates/connected-accounts.html b/templates/connected-accounts.html index b0a8125abf..9fc0eaf704 100644 --- a/templates/connected-accounts.html +++ b/templates/connected-accounts.html @@ -7,7 +7,7 @@

Connected Accounts

{% if twitter_account is None %} - {% if not user.ANON and user == participant %} + {% if not user.ANON and user.participant == participant %} Connect a Twitter account. {% else %} No Twitter account connected. @@ -31,7 +31,7 @@

Connected Accounts

{% if github_account is None %} - {% if not user.ANON and user == participant %} + {% if not user.ANON and user.participant == participant %} Connect a GitHub account. {% else %} No GitHub account connected. @@ -54,7 +54,7 @@

Connected Accounts

{% if bitbucket_account is None %} - {% if not user.ANON and user == participant %} + {% if not user.ANON and user.participant == participant %} Connect a Bitbucket account. {% else %} No Bitbucket account connected. @@ -78,7 +78,7 @@

Connected Accounts

{% if bountysource_account is None %} - {% if not user.ANON and user == participant %} + {% if not user.ANON and user.participant == participant %} Connect a Bountysource account. {% else %} No Bountysource account connected. @@ -101,7 +101,7 @@

Connected Accounts

- {% if user == participant %} + {% if user.participant == participant %} Credit card: