Skip to content

Commit

Permalink
Add models for each table in the database, plus User. Relates to grat…
Browse files Browse the repository at this point in the history
…ipay#446

Basically, I went ahead and reconstructed each table in the database as
a mapping class. In addition, I recreated the User class that subclassed
Participant class previously.

There is still clean up that needs to be done here.

Signed-off-by: Joonas Bergius <[email protected]>
  • Loading branch information
joonas committed Jan 15, 2013
1 parent 25ee218 commit 98087f1
Show file tree
Hide file tree
Showing 9 changed files with 306 additions and 0 deletions.
10 changes: 10 additions & 0 deletions gittip/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from gittip.models.absorption import Absorption
from gittip.models.elsewhere import Elsewhere
from gittip.models.exchange import Exchange
from gittip.models.participant import Participant
from gittip.models.payday import Payday
from gittip.models.tip import Tip
from gittip.models.transfer import Transfer
from gittip.models.user import User

all = [Elsewhere, Exchange, Participant, Payday, Tip, Transfer, User]
18 changes: 18 additions & 0 deletions gittip/models/absorption.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from sqlalchemy.schema import Column, ForeignKey
from sqlalchemy.types import Integer, Text, TIMESTAMP

from gittip.orm import Base

class Absorption(Base):
__tablename__ = 'absorptions'

id = Column(Integer, nullable=False, primary_key=True)
timestamp = Column(TIMESTAMP(timezone=True), nullable=False,
default="now()")
absorbed_was = Column(Text, nullable=False)
absorbed_by = Column(Text, ForeignKey("participants.id",
onupdate="CASCADE",
ondelete="RESTRICT"), nullable=False)
archived_as = Column(Text, ForeignKey("participants.id",
onupdate="RESTRICT",
ondelete="RESTRICT"), nullable=False)
30 changes: 30 additions & 0 deletions gittip/models/elsewhere.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from sqlalchemy.dialects.postgresql.hstore import HSTORE
from sqlalchemy.schema import Column, UniqueConstraint, ForeignKey
from sqlalchemy.types import Integer, Text, Boolean

from gittip.orm import Base

class Elsewhere(Base):
__tablename__ = 'elsewhere'
__table_args__ = (
UniqueConstraint('platform', 'participant_id',
name='elsewhere_platform_participant_id_key'),
UniqueConstraint('platform', 'user_id',
name='elsewhere_platform_user_id_key')
)

id = Column(Integer, nullable=False, primary_key=True)
platform = Column(Text, nullable=False)
user_id = Column(Text, nullable=False)
user_info = Column(HSTORE)
is_locked = Column(Boolean, default=False, nullable=False)
participant_id = Column(Text, ForeignKey("participants.id"), nullable=False)

def resolve_unclaimed(self):
if self.platform == 'github':
out = '/on/github/%s/' % self.user_info['login']
elif self.platform == 'twitter':
out = '/on/twitter/%s/' % self.user_info['screen_name']
else:
out = None
return out
16 changes: 16 additions & 0 deletions gittip/models/exchange.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from sqlalchemy.schema import Column, ForeignKey
from sqlalchemy.types import Integer, Numeric, Text, TIMESTAMP

from gittip.orm import Base

class Exchange(Base):
__tablename__ = 'exchanges'

id = Column(Integer, nullable=False, primary_key=True)
timestamp = Column(TIMESTAMP(timezone=True), nullable=False,
default="now()")
amount = Column(Numeric(precision=35, scale=2), nullable=False)
fee = Column(Numeric(precision=35, scale=2), nullable=False)
participant_id = Column(Text, ForeignKey("participants.id",
onupdate="CASCADE", ondelete="RESTRICT"),
nullable=False)
123 changes: 123 additions & 0 deletions gittip/models/participant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import datetime
from decimal import Decimal

import pytz
from sqlalchemy import select, func
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import relationship
from sqlalchemy.schema import Column, CheckConstraint, UniqueConstraint
from sqlalchemy.types import Text, TIMESTAMP, Boolean, Numeric
from aspen import Response

import gittip
from gittip.orm import Base, db
from gittip.models import Elsewhere
# This is loaded for now to maintain functionality until the class is fully
# migrated over to doing everything using SQLAlchemy
from gittip.participant import Participant as ParticipantClass

ASCII_ALLOWED_IN_PARTICIPANT_ID = set("0123456789"
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
".,-_;:@ ")

class Participant(Base):
__tablename__ = "participants"
__table_args__ = (
UniqueConstraint("session_token",
name="participants_session_token_key"),
)

id = Column(Text, nullable=False, primary_key=True)
statement = Column(Text, nullable=False)
stripe_customer_id = Column(Text)
last_bill_result = Column(Text)
session_token = Column(Text)
session_expires = Column(TIMESTAMP(timezone=True), default="now()")
ctime = Column(TIMESTAMP(timezone=True), nullable=False, default="now()")
claimed_time = Column(TIMESTAMP(timezone=True))
is_admin = Column(Boolean, nullable=False, default=False)
balance = Column(Numeric(precision=35, scale=2),
CheckConstraint("balance >= 0", name="min_balance"),
default=0.0, nullable=False)
pending = Column(Numeric(precision=35, scale=2), default=None)
anonymous = Column(Boolean, default=False, nullable=False)
goal = Column(Numeric(precision=35, scale=2), default=None)
balanced_account_uri = Column(Text)
last_ach_result = Column(Text)
is_suspicious = Column(Boolean)

### Relations ###
accounts_elsewhere = relationship("Elsewhere", backref="participant",
lazy="dynamic")
exchanges = relationship("Exchange", backref="participant")
# TODO: Once tippee/tipper are renamed to tippee_id/tipper_idd, we can go
# ahead and drop the foreign_keys & rename backrefs to tipper/tippee
tipper_in = relationship("Tip", backref="tipper_participant",
foreign_keys="Tip.tipper", lazy="dynamic")
tippee_in = relationship("Tip", backref="tippee_participant",
foreign_keys="Tip.tippee", lazy="dynamic")
transferer = relationship("Transfer", backref="transferer",
foreign_keys="Transfer.tipper")
trasnferee = relationship("Transfer", backref="transferee",
foreign_keys="Transfer.tippee")

def resolve_unclaimed(self):
if self.accounts_elsewhere:
return self.accounts_elsewhere[0].resolve_unclaimed()
else:
return None

def set_as_claimed(self):
self.claimed_time = datetime.datetime.now(pytz.utc)
self.save()

def change_id(self, desired_id):
ParticipantClass(self.id).change_id(desired_id)

def get_accounts_elsewhere(self):
github_account = twitter_account = None
for account in self.accounts_elsewhere.all():
if account.platform == "github":
github_account = account
elif account.platform == "twitter":
twitter_account = account
return (github_account, twitter_account)

def get_giving_for_profile(self):
return ParticipantClass(self.id).get_giving_for_profile()

def get_tip_to(self, tippee):
tip = self.tipper_in.filter_by(tippee=tippee).first()

if tip:
amount = tip.amount
else:
amount = Decimal('0.0')

return amount

@property
def dollars_giving(self):
return sum(tip.amount for tip in self.tipper_in)

@property
def dollars_receiving(self):
return sum(tip.amount for tip in self.tippee_in)

def get_number_of_backers(self):
return ParticipantClass(self.id).get_number_of_backers()

def get_chart_of_receiving(self):
# TODO: Move the query in to this class.
return ParticipantClass(self.id).get_chart_of_receiving()

def get_giving_for_profile(self, db=None):
return ParticipantClass(self.id).get_giving_for_profile(db)

def get_tips_and_total(self, for_payday=False, db=None):
return ParticipantClass(self.id).get_tips_and_total(for_payday, db)

def take_over(self, account_elsewhere, have_confirmation=False):
ParticipantClass(self.id).take_over(account_elsewhere,
have_confirmation)
36 changes: 36 additions & 0 deletions gittip/models/payday.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import datetime

import pytz
from sqlalchemy.schema import Column, UniqueConstraint
from sqlalchemy.types import Integer, Numeric, Text, TIMESTAMP

from gittip.orm import Base

class Payday(Base):
__tablename__ = 'payday'
__table_args__ = (
UniqueConstraint('ts_end', name='paydays_ts_end_key'),
)

# TODO: Move this to a different module?
EPOCH = datetime.datetime(1970, 1, 1, tzinfo=pytz.utc)

id = Column(Integer, nullable=False, primary_key=True)
ts_start = Column(TIMESTAMP(timezone=True), nullable=False,
default="now()")
ts_end = Column(TIMESTAMP(timezone=True), nullable=False,
default=EPOCH)
nparticipants = Column(Integer, default=0)
ntippers = Column(Integer, default=0)
ntips = Column(Integer, default=0)
ntransfers = Column(Integer, default=0)
transfer_volume = Column(Numeric(precision=35, scale=2), default=0.0)
ncc_failing = Column(Integer, default=0)
ncc_missing = Column(Integer, default=0)
ncharges = Column(Integer, default=0)
charge_volume = Column(Numeric(precision=35, scale=2), default=0.0)
charge_fees_volume = Column(Numeric(precision=35, scale=2), default=0.0)
nachs = Column(Integer, default=0)
ach_volume = Column(Numeric(precision=35, scale=2), default=0.0)
ach_fees_volume = Column(Numeric(precision=35, scale=2), default=0.0)
nach_failing = Column(Integer, default=0)
16 changes: 16 additions & 0 deletions gittip/models/tip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from sqlalchemy.schema import Column, ForeignKey
from sqlalchemy.types import Integer, Numeric, Text, TIMESTAMP

from gittip.orm import Base

class Tip(Base):
__tablename__ = 'tips'

id = Column(Integer, nullable=False, primary_key=True)
ctime = Column(TIMESTAMP(timezone=True), nullable=False)
mtime = Column(TIMESTAMP(timezone=True), nullable=False, default="now()")
tipper = Column(Text, ForeignKey("participants.id", onupdate="CASCADE",
ondelete="RESTRICT"), nullable=False)
tippee = Column(Text, ForeignKey("participants.id", onupdate="CASCADE",
ondelete="RESTRICT"), nullable=False)
amount = Column(Numeric(precision=35, scale=2), nullable=False)
16 changes: 16 additions & 0 deletions gittip/models/transfer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from sqlalchemy.schema import Column, ForeignKey
from sqlalchemy.types import Integer, Numeric, Text, TIMESTAMP

from gittip.orm import Base

class Transfer(Base):
__tablename__ = 'transfers'

id = Column(Integer, nullable=False, primary_key=True)
timestamp = Column(TIMESTAMP(timezone=True), nullable=False,
default="now()")
tipper = Column(Text, ForeignKey("participants.id", onupdate="CASCADE",
ondelete="RESTRICT"), nullable=False)
tippee = Column(Text, ForeignKey("participants.id", onupdate="CASCADE",
ondelete="RESTRICT"), nullable=False)
amount = Column(Numeric(precision=35, scale=2), nullable=False)
41 changes: 41 additions & 0 deletions gittip/models/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import uuid

from gittip.models.participant import Participant

class User(Participant):
"""Represent a website user.
Every current website user is also a participant, though if the user is
anonymous then the methods from Participant will fail with NoParticipantId.
"""

@classmethod
def from_session_token(cls, token):
user = User.query.filter_by(session_token=token).first()
if user and not user.is_suspicious:
user = user
else:
user = User()
return user

@classmethod
def from_id(cls, user_id):
user = User.query.filter_by(id=user_id).first()
if user and not user.is_suspicious:
user.session_token = uuid.uuid4().hex
user.save()
# attrs = participant.attrs_dict()
else:
user = User()
return user

@property
def ADMIN(self):
return self.id is not None and self.is_admin

@property
def ANON(self):
return self.id is None

def __unicode__(self):
return '<User: %s>' % getattr(self, 'id', 'Anonymous')

0 comments on commit 98087f1

Please sign in to comment.