diff --git a/README.md b/README.md index 9d6f6e6a71..525052cfb3 100644 --- a/README.md +++ b/README.md @@ -423,7 +423,7 @@ some fake data, so that more of the site is functional, run this command: API === -The Gratipay API is comprised of these six endpoints: +The Gratipay API is comprised of these four endpoints: **[/about/charts.json](https://gratipay.com/about/charts.json)** ([source](https://github.com/gratipay/gratipay.com/tree/master/www/about/charts.json.spt))—public—Returns @@ -440,28 +440,12 @@ charts page used to use this. an object giving a point-in-time snapshot of Gratipay. The [stats](https://gratipay.com/about/stats.html) page displays the same info. -**/`%username`/charts.json** -([example](https://gratipay.com/Gratipay/charts.json), -[source](https://github.com/gratipay/gratipay.com/tree/master/www/%25username/charts.json.spt))—public—Returns -an array of objects, one per week, showing aggregate numbers over time for the -given user. - **/`%username`/public.json** ([example](https://gratipay.com/Gratipay/public.json), [source](https://github.com/gratipay/gratipay.com/tree/master/www/%25username/public.json.spt))—public—Returns an object with these keys: - - "receiving"—an estimate of the amount the given participant will - receive this week - - - "my_tip"—logged-in user's tip to the Gratipay participant in - question; possible values are: - - - `undefined` (key not present)—there is no logged-in user - - "self"—logged-in user is the participant in question - - `null`—user has never tipped this participant - - "0.00"—user used to tip this participant - - "3.00"—user tips this participant the given amount -

+ - "taking"—an estimate of the amount the given participant will + take from Teams this week - "elsewhere"—participant's connected accounts elsewhere; returns an object with these keys: @@ -479,34 +463,6 @@ given user. - `http://www.openstreetmap.org/user/%openstreetmap_username` -**/`%username`/tips.json** -([source](https://github.com/gratipay/gratipay.com/tree/master/www/%25username/tips.json.spt))—private—Responds -to `GET` with an array of objects representing your current tips. `POST` the -same structure back in order to update tips in bulk (be sure to set -`Content-Type` to `application/json` instead of -`application/x-www-form-urlencoded`). You can `POST` a partial array to update -a subset of your tips. The response to a `POST` will be only the subset you -updated. If the `amount` is `"error"` then there will also be an `error` -attribute with a one-word error code. If you include an `also_prune` key in the -querystring (not the body!) with a value of `yes`, `true`, or `1`, then any -tips not in the array you `POST` will be zeroed out. - -NOTE: The amounts must be encoded as a string (rather than a number). -Additionally, currently, the only supported platform is 'gratipay' ('gittip' -still works for backwards-compatibility). - -This endpoint requires authentication. Look for your user ID and API key on your -[account page](https://gratipay.com/about/me/settings/), and pass them using basic -auth. E.g.: - -``` -curl https://gratipay.com/foobar/tips.json \ - -u $userid:$api_key \ - -X POST \ - -d'[{"username":"bazbuz", "platform":"gratipay", "amount": "1.00"}]' \ - -H"Content-Type: application/json" -``` - API Implementations ------------------- diff --git a/gratipay/billing/exchanges.py b/gratipay/billing/exchanges.py index 424e37d771..a39806ba0d 100644 --- a/gratipay/billing/exchanges.py +++ b/gratipay/billing/exchanges.py @@ -311,6 +311,7 @@ def record_exchange_result(db, exchange_id, status, error, participant): """, locals()) assert participant.username == username assert isinstance(route, ExchangeRoute) + route.set_attributes(participant=participant) # XXX Red hot hack! if amount < 0: amount -= fee @@ -325,7 +326,7 @@ def propagate_exchange(cursor, participant, route, error, amount): """Propagates an exchange's result to the participant's balance and the route's status. """ - route.update_error(error or '', propagate=False) + route.update_error(error or '') new_balance = cursor.one(""" UPDATE participants SET balance=(balance + %s) diff --git a/gratipay/billing/payday.py b/gratipay/billing/payday.py index ab29ad2801..717cf8f49d 100644 --- a/gratipay/billing/payday.py +++ b/gratipay/billing/payday.py @@ -29,9 +29,6 @@ with open('sql/payday.sql') as f: PAYDAY = f.read() -with open('sql/fake_payday.sql') as f: - FAKE_PAYDAY = f.read() - class ExceptionWrapped(Exception): pass @@ -80,7 +77,6 @@ class Payday(object): update_balances take_over_balances update_stats - update_cached_amounts end """ @@ -137,7 +133,6 @@ def run(self): self.mark_stage_done() if self.stage < 2: self.update_stats() - self.update_cached_amounts() self.mark_stage_done() self.end() @@ -462,12 +457,6 @@ def update_stats(self): log("Updated payday stats.") - def update_cached_amounts(self): - with self.db.get_cursor() as cursor: - cursor.execute(FAKE_PAYDAY) - log("Updated receiving amounts.") - - def end(self): self.ts_end = self.db.one("""\ diff --git a/gratipay/models/exchange_route.py b/gratipay/models/exchange_route.py index 2ea41b87c5..473415f720 100644 --- a/gratipay/models/exchange_route.py +++ b/gratipay/models/exchange_route.py @@ -15,11 +15,15 @@ def __bool__(self): @classmethod def from_id(cls, id): - return cls.db.one(""" + r = cls.db.one(""" SELECT r.*::exchange_routes FROM exchange_routes r WHERE id = %(id)s """, locals()) + if r: + from gratipay.models.participant import Participant # XXX Red hot hack! + r.set_attributes(participant=Participant.from_id(r.participant)) + return r @classmethod def from_network(cls, participant, network): @@ -31,7 +35,7 @@ def from_network(cls, participant, network): AND network = %(network)s """, locals()) if r: - r.__dict__['participant'] = participant + r.set_attributes(participant=participant) return r @classmethod @@ -45,7 +49,7 @@ def from_address(cls, participant, network, address): AND address = %(address)s """, locals()) if r: - r.__dict__['participant'] = participant + r.set_attributes(participant=participant) return r @classmethod @@ -57,9 +61,9 @@ def insert(cls, participant, network, address, error='', fee_cap=None): VALUES (%(participant_id)s, %(network)s, %(address)s, %(error)s, %(fee_cap)s) RETURNING exchange_routes.*::exchange_routes """, locals()) - if network == 'balanced-cc': + if network == 'braintree-cc': participant.update_giving_and_teams() - r.__dict__['participant'] = participant + r.set_attributes(participant=participant) return r def invalidate(self): @@ -69,11 +73,12 @@ def invalidate(self): # For Paypal, we remove the record entirely to prevent # an integrity error if the user tries to add the route again if self.network == 'paypal': + # XXX This doesn't sound right. Doesn't this corrupt history pages? self.db.run("DELETE FROM exchange_routes WHERE id=%s", (self.id,)) else: self.update_error('invalidated') - def update_error(self, new_error, propagate=True): + def update_error(self, new_error): id = self.id old_error = self.error if old_error == 'invalidated': @@ -85,9 +90,19 @@ def update_error(self, new_error, propagate=True): """, locals()) self.set_attributes(error=new_error) - # Update the receiving amounts of tippees if requested and necessary - if not propagate or self.network != 'balanced-cc': + # Update cached amounts if requested and necessary + if self.network != 'braintree-cc': return if self.participant.is_suspicious or bool(new_error) == bool(old_error): return - self.participant.update_giving_and_teams() + + + # XXX *White* hot hack! + # ===================== + # During payday, participant is a record from a select of + # payday_participants (or whatever), *not* an actual Participant + # object. We need the real deal so we can use a method on it ... + + from gratipay.models.participant import Participant + participant = Participant.from_username(self.participant.username) + participant.update_giving_and_teams() diff --git a/gratipay/models/participant.py b/gratipay/models/participant.py index 4a32d8d8d5..3cc7348895 100644 --- a/gratipay/models/participant.py +++ b/gratipay/models/participant.py @@ -1,12 +1,4 @@ -"""*Participant* is the name Gratipay gives to people and groups that are known -to Gratipay. We've got a ``participants`` table in the database, and a -:py:class:`Participant` class that we define here. We distinguish several kinds -of participant, based on certain properties. - - - *Stub* participants - - *Organizations* are plural participants - - *Teams* are plural participants with members - +"""Participants on Gratipay give payments and take payroll. """ from __future__ import print_function, unicode_literals @@ -42,7 +34,6 @@ ) from gratipay.models import add_event -from gratipay.models._mixin_team import MixinTeam from gratipay.models.account_elsewhere import AccountElsewhere from gratipay.models.exchange_route import ExchangeRoute from gratipay.models.team import Team @@ -61,7 +52,7 @@ USERNAME_MAX_SIZE = 32 -class Participant(Model, MixinTeam): +class Participant(Model): """Represent a Gratipay participant. """ @@ -257,7 +248,7 @@ def upsert_statement(self, lang, statement): @property def usage(self): - return max(self.giving, self.receiving) + return max(self.giving, self.taking) @property def suggested_payment(self): @@ -382,7 +373,6 @@ def clear_personal_information(self, cursor): UPDATE participants SET anonymous_giving=False - , anonymous_receiving=False , number='singular' , avatar_url=NULL , email_address=NULL @@ -390,8 +380,7 @@ def clear_personal_information(self, cursor): , session_token=NULL , session_expires=now() , giving=0 - , receiving=0 - , npatrons=0 + , taking=0 WHERE username=%(username)s RETURNING *; @@ -959,51 +948,36 @@ def update_giving(self, cursor=None): RETURNING * """, (self.username,)) - giving = (cursor or self.db).one(""" + r = (cursor or self.db).one(""" + WITH pi AS ( + SELECT amount + FROM current_payment_instructions cpi + JOIN teams t ON t.slug = cpi.team + WHERE participant = %(username)s + AND amount > 0 + AND is_funded + AND t.is_approved + ) UPDATE participants p - SET giving = COALESCE(( - SELECT sum(amount) - FROM current_payment_instructions cpi - JOIN teams t ON t.slug = cpi.team - WHERE participant = %(username)s - AND amount > 0 - AND is_funded - AND t.is_approved - ), 0) + SET giving = COALESCE((SELECT sum(amount) FROM pi), 0) + , ngiving_to = COALESCE((SELECT count(amount) FROM pi), 0) WHERE p.username=%(username)s - RETURNING giving + RETURNING giving, ngiving_to """, dict(username=self.username)) - self.set_attributes(giving=giving) + self.set_attributes(giving=r.giving, ngiving_to=r.ngiving_to) return updated - def update_receiving(self, cursor=None): - if self.IS_PLURAL: - old_takes = self.compute_actual_takes(cursor=cursor) - r = (cursor or self.db).one(""" - WITH our_tips AS ( - SELECT amount - FROM current_tips - JOIN participants p2 ON p2.username = tipper - WHERE tippee = %(username)s - AND p2.is_suspicious IS NOT true - AND amount > 0 - AND is_funded - ) - UPDATE participants p - SET receiving = (COALESCE(( - SELECT sum(amount) - FROM our_tips - ), 0) + taking) - , npatrons = COALESCE((SELECT count(*) FROM our_tips), 0) - WHERE p.username = %(username)s - RETURNING receiving, npatrons + def update_taking(self, cursor=None): + (cursor or self.db).run(""" + + UPDATE participants + SET taking=COALESCE((SELECT sum(receiving) FROM teams WHERE owner=%(username)s), 0) + , ntaking_from=COALESCE((SELECT count(*) FROM teams WHERE owner=%(username)s), 0) + WHERE username=%(username)s + """, dict(username=self.username)) - self.set_attributes(receiving=r.receiving, npatrons=r.npatrons) - if self.IS_PLURAL: - new_takes = self.compute_actual_takes(cursor=cursor) - self.update_taking(old_takes, new_takes, cursor=cursor) def update_is_free_rider(self, is_free_rider, cursor=None): @@ -1120,12 +1094,12 @@ def change_username(self, suggested): def get_og_title(self): out = self.username - receiving = self.receiving giving = self.giving - if (giving > receiving) and not self.anonymous_giving: + taking = self.taking + if (giving > taking) and not self.anonymous_giving: out += " gives $%.2f/wk" % giving - elif receiving > 0 and not self.anonymous_receiving: - out += " receives $%.2f/wk" % receiving + elif taking > 0: + out += " takes $%.2f/wk" % taking else: out += " is" return out + " on Gratipay" @@ -1170,7 +1144,6 @@ def reserve(cursor, username): , session_token=NULL , session_expires=now() , giving = 0 - , receiving = 0 , taking = 0 WHERE username=%s RETURNING username @@ -1522,8 +1495,8 @@ def take_over(self, account, have_confirmation=False): self.update_avatar() - # Note: the order matters here, receiving needs to be updated before giving - self.update_receiving() + # Note: the order ... doesn't actually matter here. + self.update_taking() self.update_giving() def to_dict(self, details=False, inquirer=None): @@ -1532,47 +1505,27 @@ def to_dict(self, details=False, inquirer=None): , 'avatar': self.avatar_url , 'number': self.number , 'on': 'gratipay' - } + } if not details: return output - # Key: npatrons - output['npatrons'] = self.npatrons - - # Key: receiving + # Key: taking # Values: - # null - user is receiving anonymously - # 3.00 - user receives this amount in tips - if not self.anonymous_receiving: - receiving = str(self.receiving) - else: - receiving = None - output['receiving'] = receiving + # 3.00 - user takes this amount in payroll + output['taking'] = str(self.taking) + output['ntaking_from'] = self.ntaking_from # Key: giving # Values: # null - user is giving anonymously - # 3.00 - user gives this amount in tips - if not self.anonymous_giving: - giving = str(self.giving) - else: + # 3.00 - user gives this amount + if self.anonymous_giving: giving = None + else: + giving = str(self.giving) output['giving'] = giving - - # Key: my_tip - # Values: - # undefined - user is not authenticated - # "self" - user == participant - # null - user has never tipped this person - # 0.00 - user used to tip this person but now doesn't - # 3.00 - user tips this person this amount - if inquirer: - if inquirer.username == self.username: - my_tip = 'self' - else: - my_tip = inquirer.get_tip_to(self.username)['amount'] - output['my_tip'] = str(my_tip) + output['ngiving_to'] = self.ngiving_to # Key: elsewhere accounts = self.get_accounts_elsewhere() diff --git a/gratipay/models/team.py b/gratipay/models/team.py index 9c24eb599d..fd2fa32350 100644 --- a/gratipay/models/team.py +++ b/gratipay/models/team.py @@ -1,4 +1,4 @@ -"""Teams on Gratipay are plural participants with members. +"""Teams on Gratipay receive payments and distribute payroll. """ from postgres.orm import Model @@ -85,16 +85,24 @@ def update_receiving(self, cursor=None): AND is_funded ) UPDATE teams t - SET receiving = (COALESCE(( - SELECT sum(amount) - FROM our_receiving - ), 0)) - , nsupporters = COALESCE((SELECT count(*) FROM our_receiving), 0) + SET receiving = COALESCE((SELECT sum(amount) FROM our_receiving), 0) + , nreceiving_from = COALESCE((SELECT count(*) FROM our_receiving), 0) + , distributing = COALESCE((SELECT sum(amount) FROM our_receiving), 0) + , ndistributing_to = 1 WHERE t.slug = %(slug)s - RETURNING receiving, nsupporters + RETURNING receiving, nreceiving_from, distributing, ndistributing_to """, dict(slug=self.slug)) - self.set_attributes(receiving=r.receiving, nsupporters=r.nsupporters) + + # This next step is easy for now since we don't have payroll. + from gratipay.models.participant import Participant + Participant.from_username(self.owner).update_taking() + + self.set_attributes( receiving=r.receiving + , nreceiving_from=r.nreceiving_from + , distributing=r.distributing + , ndistributing_to=r.ndistributing_to + ) @property def status(self): diff --git a/gratipay/testing/__init__.py b/gratipay/testing/__init__.py index 4ddb31b449..d127dbbe32 100644 --- a/gratipay/testing/__init__.py +++ b/gratipay/testing/__init__.py @@ -235,7 +235,7 @@ def make_exchange(self, route, amount, fee, participant, status='succeeded', err return e_id - def make_tip(self, tipper, tippee, amount, update_self=True, update_tippee=True, cursor=None): + def make_tip(self, tipper, tippee, amount, cursor=None): """Given a Participant or username, and amount as str, returns a dict. We INSERT instead of UPDATE, so that we have history to explore. The @@ -286,12 +286,6 @@ def make_tip(self, tipper, tippee, amount, update_self=True, update_tippee=True, args = dict(tipper=tipper.username, tippee=tippee.username, amount=amount) t = (cursor or self.db).one(NEW_TIP, args) - if update_self: - # Update giving amount of tipper - tipper.update_giving(cursor) - if update_tippee: - # Update receiving amount of tippee - tippee.update_receiving(cursor) if tippee.username == 'Gratipay': # Update whether the tipper is using Gratipay for free tipper.update_is_free_rider(None if amount == 0 else False, cursor) diff --git a/gratipay/utils/__init__.py b/gratipay/utils/__init__.py index f0710318b4..867f4aa7f5 100644 --- a/gratipay/utils/__init__.py +++ b/gratipay/utils/__init__.py @@ -140,12 +140,12 @@ def update_global_stats(website): website.gnactive = stats[0] website.gtransfer_volume = stats[1] - nbackers = website.db.one(""" - SELECT npatrons - FROM participants - WHERE username = 'Gratipay' + nreceiving_from = website.db.one(""" + SELECT nreceiving_from + FROM teams + WHERE slug = 'Gratipay' """, default=0) - website.support_current = cur = int(round(nbackers / stats[0] * 100)) if stats[0] else 0 + website.support_current = cur = int(round(nreceiving_from / stats[0] * 100)) if stats[0] else 0 if cur < 10: goal = 20 elif cur < 15: goal = 30 elif cur < 25: goal = 40 diff --git a/gratipay/utils/fake_data.py b/gratipay/utils/fake_data.py index 5b7e65f340..fcb573aa91 100644 --- a/gratipay/utils/fake_data.py +++ b/gratipay/utils/fake_data.py @@ -61,7 +61,6 @@ def fake_participant(db, number="singular", is_admin=False): , is_admin=is_admin , balance=0 , anonymous_giving=(random.randrange(5) == 0) - , anonymous_receiving=(number != 'plural' and random.randrange(5) == 0) , balanced_customer_href=faker.uri() , is_suspicious=False , claimed_time=faker.date_time_this_year() @@ -99,7 +98,7 @@ def fake_team(db, teamowner): , owner=teamowner.username , is_approved=random.sample(isapproved,1)[0] , receiving=0.1 - , nmembers=3 + , nreceiving_from=3 ) except IntegrityError: return fake_team(db, teamowner) diff --git a/js/gratipay/payments.js b/js/gratipay/payments.js index b4ed5e2fbb..15871e7495 100644 --- a/js/gratipay/payments.js +++ b/js/gratipay/payments.js @@ -8,7 +8,7 @@ Gratipay.payments.init = function() { root: $('.your-payment.js-edit'), success: function(data) { Gratipay.notification(data.msg, 'success'); - Gratipay.payments.afterTipChange(data); + Gratipay.payments.afterPaymentChange(data); } }); @@ -58,17 +58,17 @@ Gratipay.payments.initSupportGratipay = function() { }; -Gratipay.payments.afterTipChange = function(data) { +Gratipay.payments.afterPaymentChange = function(data) { $('.my-total-giving').text(data.total_giving_l); - $('.total-receiving[data-team="'+data.team_id+'"]').text(data.total_receiving_team_l); + $('.total-receiving[data-team="'+data.team_id+'"]').text(data.total_receiving_l); $('#payment-prompt').toggleClass('needed', data.amount > 0); - $('.nsupporters[data-team="'+data.team_id+'"]').text(data.nsupporters); + $('.nreceiving-from[data-team="'+data.team_id+'"]').text(data.nreceiving_from); var $your_payment = $('.your-payment[data-team="'+data.team_id+'"]'); - if ($your_payment) { + if ($your_payment.length === 1) { var $input = $your_payment.find('input'); $input[0].defaultValue = $input.val(); - $your_payment.find('span.amount').text(data.amount_l); + $your_payment.find('.view span.amount').text(data.amount_l); $your_payment.find('.edit').toggleClass('not-zero', data.amount > 0); $your_payment.find('.stop').toggleClass('zero', data.amount === 0); } @@ -80,7 +80,7 @@ Gratipay.payments.set = function(team, amount, callback) { // send request to set up a recurring payment $.post('/' + team + '/payment-instruction.json', { amount: amount }, function(data) { if (callback) callback(data); - Gratipay.payments.afterTipChange(data); + Gratipay.payments.afterPaymentChange(data); }) .fail(Gratipay.error); }; diff --git a/js/gratipay/settings.js b/js/gratipay/settings.js index c9a3a1af95..c45efe7999 100644 --- a/js/gratipay/settings.js +++ b/js/gratipay/settings.js @@ -15,39 +15,6 @@ Gratipay.settings.init = function() { }); - // Wire up account type knob. - // ========================== - - $('.number input').click(function(e) { - var $input = $(this); - - e.preventDefault(); - - function post(confirmed) { - jQuery.ajax({ - url: '../number.json', - type: 'POST', - data: { - number: $input.val(), - confirmed: confirmed - }, - success: function(data) { - if (data.confirm) { - if (confirm(data.confirm)) return post(true); - return; - } - if (data.number) { - $input.prop('checked', true); - Gratipay.notification(data.msg || "Success", 'success'); - $('li.members').toggleClass('hidden', data.number !== 'plural'); - } - }, - error: Gratipay.error, - }); - } - post(); - }); - // Wire up privacy settings. // ========================= diff --git a/scss/components/banner.scss b/scss/components/banner.scss index f356910d35..f6df51be08 100644 --- a/scss/components/banner.scss +++ b/scss/components/banner.scss @@ -1,13 +1,12 @@ #banner { - - a { - display: block; - } + position: relative; + z-index: 0; .avatar { position: relative; height: 192px; - width: 256px; // to allow for blur + width: 100%; + z-index: 2; overflow: hidden; img { @@ -31,18 +30,25 @@ left: 136px; bottom: 24px; box-shadow: none; + z-index: 4; } } .is-suspicious-label { text-align: right; - font-size: 10px; + font-size: 12px; text-transform: uppercase; display: block; color: $white; position: absolute; - bottom: 0px; - right: 80px; + bottom: 24px; + left: 24px; + z-index: 3; + background: $black; + border: 2px solid $white; + @include border-radius(5px); + + padding: 1px 8px; } &.is-suspicious { @@ -54,13 +60,54 @@ position: absolute; top: 16px; right: 16px; + z-index: 2; color: $white; text-shadow: -1px 1px 1px rgba($brown, 1); text-align: right; + background: $black; + padding: 3px 7px; + @include border-radius(5px); + box-shadow: 0px 0px 32px 16px rgba($black, 0.5); + border: 2px solid $white; + + p { + margin: 0; + padding: 0; + } + + table { + margin: 0 0 6px auto; + th { + text-align: right; + } + td { + padding: 0 0 0 12px; + &:first-child { + padding-left: 0; + } + } + .label { + text-align: center; + } + } + } + + .admin-details { + position: absolute; + top: 24px; + left: 24px; + max-width: 45%; + padding: 2px 16px 1px; + color: white; + background: $black; + border: 2px solid $white; + z-index: 2; + @include border-radius(5px); + box-shadow: 0px 0px 32px 16px rgba($black, 0.5); a { - color: $white; text-decoration: underline; + color: $white; } } @@ -69,6 +116,7 @@ position: absolute; bottom: 4px; right: 16px; + z-index: 2; text-align: right; color: $white; text-shadow: -1px 1px 1px rgba($brown, 1); diff --git a/scss/components/nav.scss b/scss/components/nav.scss index e61794becf..f98e6c2567 100644 --- a/scss/components/nav.scss +++ b/scss/components/nav.scss @@ -121,6 +121,7 @@ a { display: inline-block; padding: 16px 16px 12px; + font-weight: normal; .icon { font: normal 20px/20px 'icomoon'; diff --git a/scss/layouts/responsiveness.scss b/scss/layouts/responsiveness.scss index c1488efb1a..fc0aa55a7d 100644 --- a/scss/layouts/responsiveness.scss +++ b/scss/layouts/responsiveness.scss @@ -1,4 +1,7 @@ @media (max-width: 600px) { + .luxury { + display: none; + } #header { text-align: center; h1 { @@ -11,7 +14,6 @@ height: 128px; .avatar { height: 128px; - width: 144px; img { width: 112px; height: 112px; @@ -32,9 +34,24 @@ } } .is-suspicious-label { - display: none; + font-size: 10px; + bottom: 13px; + left: 13px; } } + .details { + font-size: 10px; + box-shadow: 0px 0px 16px 8px rgba($black, 0.5); + @include border-radius(3px); + top: 8px; + right: 8px; + } + .admin-details { + top: 13px; + left: 13px; + font-size: 10px; + padding: 1px 8px; + } h1 { font-size: 32px; line-height: 40px; diff --git a/sql/branch.sql b/sql/branch.sql new file mode 100644 index 0000000000..bb4733d8b2 --- /dev/null +++ b/sql/branch.sql @@ -0,0 +1,14 @@ +BEGIN; + + ALTER TABLE participants DROP COLUMN anonymous_receiving; + ALTER TABLE participants DROP COLUMN npatrons; + ALTER TABLE participants DROP COLUMN receiving; + + ALTER TABLE participants ADD COLUMN ngiving_to INTEGER NOT NULL DEFAULT 0; + ALTER TABLE participants ADD COLUMN ntaking_from INTEGER NOT NULL DEFAULT 0; + + ALTER TABLE teams RENAME COLUMN nsupporters TO nreceiving_from; + ALTER TABLE teams RENAME COLUMN nmembers TO ndistributing_to; + ALTER TABLE teams RENAME COLUMN payroll TO distributing; + +END; diff --git a/sql/fake_payday.sql b/sql/fake_payday.sql deleted file mode 100644 index fc2720c1e7..0000000000 --- a/sql/fake_payday.sql +++ /dev/null @@ -1,186 +0,0 @@ --- Create the necessary temporary tables and indexes - -CREATE TEMPORARY TABLE temp_participants ON COMMIT DROP AS - SELECT username - , claimed_time - , balance AS fake_balance - , 0::numeric(35,2) AS giving - , 0::numeric(35,2) AS taking - , 0::numeric(35,2) AS receiving - , 0 as npatrons - , ( SELECT count(*) - FROM exchange_routes r - WHERE r.participant = p.id - AND network = 'balanced-cc' - AND error = '' - ) > 0 AS credit_card_ok - FROM participants p - WHERE is_suspicious IS NOT true; - -CREATE UNIQUE INDEX ON temp_participants (username); - -CREATE TEMPORARY TABLE temp_tips ON COMMIT DROP AS - SELECT t.id, tipper, tippee, amount, (p2.claimed_time IS NOT NULL) AS claimed - FROM current_tips t - JOIN temp_participants p ON p.username = t.tipper - JOIN temp_participants p2 ON p2.username = t.tippee - WHERE t.amount > 0 - ORDER BY p2.claimed_time IS NULL, p.claimed_time ASC, t.ctime ASC; - -CREATE INDEX ON temp_tips (tipper); -CREATE INDEX ON temp_tips (tippee); -ALTER TABLE temp_tips ADD COLUMN is_funded boolean NOT NULL DEFAULT false; - -CREATE TEMPORARY TABLE temp_takes -( team text -, member text -, amount numeric(35,2) -) ON COMMIT DROP; - - --- Create a trigger to process tips - -CREATE OR REPLACE FUNCTION fake_tip() RETURNS trigger AS $$ - DECLARE - tipper temp_participants; - BEGIN - tipper := ( - SELECT p.*::temp_participants - FROM temp_participants p - WHERE username = NEW.tipper - ); - IF (NEW.amount > tipper.fake_balance AND NOT tipper.credit_card_ok) THEN - RETURN NULL; - END IF; - IF (NEW.claimed) THEN - UPDATE temp_participants - SET fake_balance = (fake_balance - NEW.amount) - , giving = (giving + NEW.amount) - WHERE username = NEW.tipper; - ELSE - UPDATE temp_participants - SET fake_balance = (fake_balance - NEW.amount) - WHERE username = NEW.tipper; - END IF; - UPDATE temp_participants - SET fake_balance = (fake_balance + NEW.amount) - , receiving = (receiving + NEW.amount) - , npatrons = (npatrons + 1) - WHERE username = NEW.tippee; - RETURN NEW; - END; -$$ LANGUAGE plpgsql; - -CREATE TRIGGER fake_tip BEFORE UPDATE OF is_funded ON temp_tips - FOR EACH ROW - WHEN (NEW.is_funded IS true AND OLD.is_funded IS NOT true) - EXECUTE PROCEDURE fake_tip(); - - --- Create a trigger to process takes - -CREATE OR REPLACE FUNCTION fake_take() RETURNS trigger AS $$ - DECLARE - actual_amount numeric(35,2); - team_balance numeric(35,2); - BEGIN - team_balance := ( - SELECT fake_balance - FROM temp_participants - WHERE username = NEW.team - ); - IF (team_balance <= 0) THEN RETURN NULL; END IF; - actual_amount := NEW.amount; - IF (team_balance < NEW.amount) THEN - actual_amount := team_balance; - END IF; - UPDATE temp_participants - SET fake_balance = (fake_balance - actual_amount) - WHERE username = NEW.team; - UPDATE temp_participants - SET fake_balance = (fake_balance + actual_amount) - , taking = (taking + actual_amount) - , receiving = (receiving + actual_amount) - WHERE username = NEW.member; - RETURN NULL; - END; -$$ LANGUAGE plpgsql; - -CREATE TRIGGER fake_take AFTER INSERT ON temp_takes - FOR EACH ROW EXECUTE PROCEDURE fake_take(); - - --- Create a function to settle whole tip graph - -CREATE OR REPLACE FUNCTION settle_tip_graph() RETURNS void AS $$ - DECLARE - count integer NOT NULL DEFAULT 0; - i integer := 0; - BEGIN - LOOP - i := i + 1; - WITH updated_rows AS ( - UPDATE temp_tips - SET is_funded = true - WHERE is_funded IS NOT true - RETURNING * - ) - SELECT COUNT(*) FROM updated_rows INTO count; - IF (count = 0) THEN - EXIT; - END IF; - IF (i > 50) THEN - RAISE 'Reached the maximum number of iterations'; - END IF; - END LOOP; - END; -$$ LANGUAGE plpgsql; - - --- Start fake payday - --- Step 1: tips -UPDATE temp_tips t - SET is_funded = true - FROM temp_participants p - WHERE p.username = t.tipper - AND p.credit_card_ok; - -SELECT settle_tip_graph(); - --- Step 2: team takes -INSERT INTO temp_takes - SELECT team, member, amount - FROM current_takes t - WHERE t.amount > 0 - AND t.team IN (SELECT username FROM temp_participants) - AND t.member IN (SELECT username FROM temp_participants) - ORDER BY ctime DESC; - --- Step 3: tips again -SELECT settle_tip_graph(); - --- Step 4: update the real tables -UPDATE tips t - SET is_funded = tt.is_funded - FROM temp_tips tt - WHERE t.id = tt.id - AND t.is_funded <> tt.is_funded; - -UPDATE participants p - SET giving = p2.giving - , taking = p2.taking - , receiving = p2.receiving - , npatrons = p2.npatrons - FROM temp_participants p2 - WHERE p.username = p2.username - AND ( p.giving <> p2.giving OR - p.taking <> p2.taking OR - p.receiving <> p2.receiving OR - p.npatrons <> p2.npatrons - ); - --- Clean up functions -DROP FUNCTION fake_take() CASCADE; -DROP FUNCTION fake_tip() CASCADE; -DROP FUNCTION settle_tip_graph() CASCADE; diff --git a/templates/profile-details.html b/templates/profile-details.html deleted file mode 100644 index cbd150dab3..0000000000 --- a/templates/profile-details.html +++ /dev/null @@ -1,36 +0,0 @@ -
- {% set g = participant.giving %} - {% set r = participant.receiving %} - {% set anon_giving = participant.anonymous_giving %} - {% set anon_receiving = participant.anonymous_receiving %} - {% set giving_str = '[' + _('hidden') + ']' if anon_giving else format_currency(g, "USD") %} - {% set receiving_str = '[' + _('hidden') + ']' if anon_receiving else format_currency(r, "USD") %} - - - - {% if participant.claimed_time %} -

{{ _('Joined') }} {{ to_age(participant.claimed_time, add_direction=True) }}.

- {% elif participant.is_closed %} -

Closed {{ to_age(participant.closed_time, add_direction=True) }}.

- {% else %} - {% set absorption = website.db.one( "select * from absorptions where archived_as=%s" - , (participant.username,) - ) %} -

Formerly {{ absorption.absorbed_was }}.

-

Absorbed by {{ absorption.absorbed_by }} - {{ to_age(absorption.timestamp, add_direction=True) }}.

- {% endif %} -

diff --git a/templates/profile.html b/templates/profile.html index d2fdc048fa..5f612d52ea 100644 --- a/templates/profile.html +++ b/templates/profile.html @@ -22,47 +22,64 @@ + {% elif participant.is_suspicious %}
Suspicious
{% endif %}
- {% set g = participant.giving %} - {% set r = participant.receiving %} - {% set anon_giving = participant.anonymous_giving %} - {% set anon_receiving = participant.anonymous_receiving %} - {% set giving_str = '[' + _('hidden') + ']' if anon_giving else format_currency(g, "USD") %} - {% set receiving_str = '[' + _('hidden') + ']' if anon_receiving else format_currency(r, "USD") %} - - {% if participant.claimed_time %} -

{{ _('Joined') }} {{ to_age(participant.claimed_time, add_direction=True) }}.

- {% elif participant.is_closed %} -

Closed {{ to_age(participant.closed_time, add_direction=True) }}.

- {% else %} +

{{ _('Joined') }} {{ to_age(participant.claimed_time, add_direction=True) }}

+ {% endif %} + +
+ +{% if user.ADMIN %} + {% if participant.is_closed %} +
+ Closed {{ to_age(participant.closed_time, add_direction=True) }}. +
+ {% elif not participant.claimed_time %} {% set absorption = website.db.one( "select * from absorptions where archived_as=%s" , (participant.username,) ) %} -

Formerly {{ absorption.absorbed_was }}.

-

Absorbed by {{ absorption.absorbed_by }} - {{ to_age(absorption.timestamp, add_direction=True) }}.

+

+ Was {{ absorption.absorbed_was }}; + absorbed by {{ absorption.absorbed_by }} + {{ to_age(absorption.timestamp, add_direction=True) }}. +
{% endif %} - +{% endif %} {{ super() }} {% endblock %} diff --git a/tests/py/test_billing_payday.py b/tests/py/test_billing_payday.py index a6d92b73fd..5dd1a5fb7f 100644 --- a/tests/py/test_billing_payday.py +++ b/tests/py/test_billing_payday.py @@ -82,88 +82,6 @@ def test_ncc_failing(self, cch, fch): after = self.fetch_payday() assert after['ncc_failing'] == 1 - @pytest.mark.xfail(reason="#3399") - def test_update_cached_amounts(self): - team = self.make_participant('team', claimed_time='now', number='plural') - alice = self.make_participant('alice', claimed_time='now', last_bill_result='') - bob = self.make_participant('bob', claimed_time='now') - carl = self.make_participant('carl', claimed_time='now', last_bill_result="Fail!") - dana = self.make_participant('dana', claimed_time='now') - emma = self.make_participant('emma') - alice.set_tip_to(dana, '3.00') - alice.set_tip_to(bob, '6.00') - alice.set_tip_to(emma, '1.00') - alice.set_tip_to(team, '4.00') - bob.set_tip_to(alice, '5.00') - team.add_member(bob) - team.set_take_for(bob, D('1.00'), bob) - bob.set_tip_to(dana, '2.00') # funded by bob's take - bob.set_tip_to(emma, '7.00') # not funded, insufficient receiving - carl.set_tip_to(dana, '2.08') # not funded, failing card - - def check(): - alice = Participant.from_username('alice') - bob = Participant.from_username('bob') - carl = Participant.from_username('carl') - dana = Participant.from_username('dana') - emma = Participant.from_username('emma') - assert alice.giving == D('13.00') - assert alice.receiving == D('5.00') - assert bob.giving == D('7.00') - assert bob.receiving == D('7.00') - assert bob.taking == D('1.00') - assert carl.giving == D('0.00') - assert carl.receiving == D('0.00') - assert dana.receiving == D('5.00') - assert dana.npatrons == 2 - assert emma.receiving == D('1.00') - assert emma.npatrons == 1 - funded_tips = self.db.all("SELECT amount FROM tips WHERE is_funded ORDER BY id") - assert funded_tips == [3, 6, 1, 4, 5, 2] - - # Pre-test check - check() - - # Check that update_cached_amounts doesn't mess anything up - Payday.start().update_cached_amounts() - check() - - # Check that update_cached_amounts actually updates amounts - self.db.run(""" - UPDATE tips SET is_funded = false; - UPDATE participants - SET giving = 0 - , npatrons = 0 - , receiving = 0 - , taking = 0; - """) - Payday.start().update_cached_amounts() - check() - - @pytest.mark.xfail(reason="#3399") - def test_update_cached_amounts_depth(self): - alice = self.make_participant('alice', claimed_time='now', last_bill_result='') - usernames = ('bob', 'carl', 'dana', 'emma', 'fred', 'greg') - users = [self.make_participant(username, claimed_time='now') for username in usernames] - - prev = alice - for user in reversed(users): - prev.set_tip_to(user, '1.00') - prev = user - - def check(): - for username in reversed(usernames[1:]): - user = Participant.from_username(username) - assert user.giving == D('1.00') - assert user.receiving == D('1.00') - assert user.npatrons == 1 - funded_tips = self.db.all("SELECT id FROM tips WHERE is_funded ORDER BY id") - assert len(funded_tips) == 6 - - check() - Payday.start().update_cached_amounts() - check() - @mock.patch('gratipay.billing.payday.log') def test_start_prepare(self, log): self.clear_tables() @@ -394,8 +312,6 @@ def test_payin_doesnt_make_null_payments(self): alice = self.make_participant('alice', claimed_time='now') alice.set_payment_instruction(team, 1) alice.set_payment_instruction(team, 0) - a_team = self.make_participant('a_team', claimed_time='now', number='plural') - a_team.add_member(alice) Payday.start().payin() payments = self.db.all("SELECT * FROM payments WHERE amount = 0") assert not payments diff --git a/tests/py/test_charts_json.py b/tests/py/test_charts_json.py index 6d9e7235fe..81d800d9b2 100644 --- a/tests/py/test_charts_json.py +++ b/tests/py/test_charts_json.py @@ -35,6 +35,7 @@ def run_payday(self): Payday.start().run() + @pytest.mark.xfail(reason="moved charts.json to %team/ and need to update it") def test_no_payday_returns_empty_list(self): assert json.loads(self.client.GET('/~carl/charts.json').body) == [] @@ -171,17 +172,3 @@ def test_transfer_volume(self): actual = json.loads(self.client.GET('/about/charts.json').body)[0] assert actual == expected - - @pytest.mark.xfail(reason="haven't migrated transfer_takes yet") - def test_anonymous_receiver(self): - self.run_payday() - self.run_payday() - self.client.POST('/~carl/privacy.json', - {'toggle': 'anonymous_receiving'}, - auth_as='carl') - - r = self.client.GxT('/~carl/charts.json') - assert r.code == 401 - - r = self.client.GxT('/~carl/charts.json', auth_as='alice') - assert r.code == 403 diff --git a/tests/py/test_close.py b/tests/py/test_close.py index 830cc94cd0..2de9ae3c0c 100644 --- a/tests/py/test_close.py +++ b/tests/py/test_close.py @@ -135,15 +135,13 @@ def test_cpi_clears_multiple_payment_instructions(self): def test_cpi_clears_personal_information(self, mailer): alice = self.make_participant( 'alice' , anonymous_giving=True - , anonymous_receiving=True , avatar_url='img-url' , email_address='alice@example.com' , claimed_time='now' , session_token='deadbeef' , session_expires='2000-01-01' , giving=20 - , receiving=40 - , npatrons=21 + , taking=40 ) alice.upsert_statement('en', 'not forgetting to be awesome!') alice.add_email('alice@example.net') @@ -154,14 +152,12 @@ def test_cpi_clears_personal_information(self, mailer): assert alice.get_statement(['en']) == (None, None) assert alice.anonymous_giving == new_alice.anonymous_giving == False - assert alice.anonymous_receiving == new_alice.anonymous_receiving == False assert alice.number == new_alice.number == 'singular' assert alice.avatar_url == new_alice.avatar_url == None assert alice.email_address == new_alice.email_address == None assert alice.claimed_time == new_alice.claimed_time == None assert alice.giving == new_alice.giving == 0 - assert alice.receiving == new_alice.receiving == 0 - assert alice.npatrons == new_alice.npatrons == 0 + assert alice.taking == new_alice.taking == 0 assert alice.session_token == new_alice.session_token == None assert alice.session_expires.year == new_alice.session_expires.year == date.today().year assert not alice.get_emails() diff --git a/tests/py/test_number_json.py b/tests/py/test_number_json.py deleted file mode 100644 index fa609aacff..0000000000 --- a/tests/py/test_number_json.py +++ /dev/null @@ -1,27 +0,0 @@ -from __future__ import print_function, unicode_literals - -import json - -from gratipay.testing import Harness - - -class Tests(Harness): - - def change_number(self, number, auth_as='alice', expecting_error=False): - self.make_participant('alice', claimed_time='now') - - method = self.client.POST if not expecting_error else self.client.PxST - response = method( "/~alice/number.json" - , {'number': number} - , auth_as=auth_as - ) - return response - - def test_participant_can_change_their_number(self): - response = self.change_number('plural') - actual = json.loads(response.body)['number'] - assert actual == 'plural' - - def test_invalid_is_400(self): - response = self.change_number('none', expecting_error=True) - assert response.code == 400, response.code diff --git a/tests/py/test_pages.py b/tests/py/test_pages.py index 529995f41d..6bca23a7bc 100644 --- a/tests/py/test_pages.py +++ b/tests/py/test_pages.py @@ -16,11 +16,8 @@ class TestPages(Harness): def browse(self, setup=None, **kw): - alice = self.make_participant('alice', claimed_time='now', number='plural') - exchange_id = self.make_exchange('balanced-cc', 19, 0, alice) - alice.insert_into_communities(True, 'Wonderland', 'wonderland') - alan = self.make_participant('alan', claimed_time='now') - alice.add_member(alan) + alice = self.make_participant('alice', claimed_time='now') + exchange_id = self.make_exchange('braintree-cc', 19, 0, alice) if setup: setup(alice) i = len(self.client.www_root) diff --git a/tests/py/test_privacy_json.py b/tests/py/test_privacy_json.py index c19137902c..985a888c4a 100644 --- a/tests/py/test_privacy_json.py +++ b/tests/py/test_privacy_json.py @@ -21,7 +21,6 @@ def test_participant_can_get_their_privacy_settings(self): assert actual == { 'is_searchable': True, 'anonymous_giving': False, - 'anonymous_receiving': False } def test_participant_can_toggle_is_searchable(self): @@ -46,17 +45,6 @@ def test_participant_can_toggle_anonymous_giving_back(self): actual = json.loads(response.body)['anonymous_giving'] assert actual is False - def test_participant_can_toggle_anonymous_receiving(self): - response = self.hit_privacy('POST', data={'toggle': 'anonymous_receiving'}) - actual = json.loads(response.body) - assert actual['anonymous_receiving'] is True - - def test_participant_can_toggle_anonymous_receiving_back(self): - response = self.hit_privacy('POST', data={'toggle': 'anonymous_receiving'}) - response = self.hit_privacy('POST', data={'toggle': 'anonymous_receiving'}) - actual = json.loads(response.body)['anonymous_receiving'] - assert actual is False - # Related to is-searchable def test_meta_robots_tag_added_on_opt_out(self): @@ -70,17 +58,3 @@ def test_participant_does_show_up_on_search(self): def test_participant_doesnt_show_up_on_search(self): self.hit_privacy('POST', data={'toggle': 'is_searchable'}) assert 'alice' not in self.client.GET("/search.json?q=alice").body - - # Related to anonymous-receiving - - def test_team_cannot_toggle_anonymous_receiving(self): - self.make_participant('team', claimed_time='now', number='plural') - response = self.client.PxST( - '/~team/privacy.json', - auth_as='team', - data={'toggle': 'anonymous_receiving'} - ) - actual = response.code - expected = 403 - assert actual == expected - diff --git a/tests/py/test_public_json.py b/tests/py/test_public_json.py index c0452dc68f..7ad7787576 100644 --- a/tests/py/test_public_json.py +++ b/tests/py/test_public_json.py @@ -2,9 +2,9 @@ import json -import pytest from aspen.utils import utcnow from gratipay.testing import Harness +from gratipay.models.participant import Participant class Tests(Harness): @@ -16,142 +16,40 @@ def make_participant(self, *a, **kw): def test_on_key_gives_gratipay(self): self.make_participant('alice', last_bill_result='') data = json.loads(self.client.GET('/~alice/public.json').body) - assert data['on'] == 'gratipay' - @pytest.mark.xfail(reason="#3599") - def test_anonymous_gets_receiving(self): - alice = self.make_participant('alice', last_bill_result='') - bob = self.make_participant('bob') - - alice.set_tip_to(bob, '1.00') - - data = json.loads(self.client.GET('/~bob/public.json').body) - - assert data['receiving'] == '1.00' - - @pytest.mark.xfail(reason="#3599") - def test_anonymous_does_not_get_my_tip(self): + def test_anonymous_gets_taking(self): alice = self.make_participant('alice', last_bill_result='') - bob = self.make_participant('bob') + Enterprise = self.make_team(is_approved=True) + alice.set_payment_instruction(Enterprise, '1.00') + data = json.loads(self.client.GET('/~picard/public.json').body) + assert data['taking'] == '1.00' - alice.set_tip_to(bob, '1.00') - - data = json.loads(self.client.GET('/~bob/public.json').body) - - assert data.has_key('my_tip') == False - - @pytest.mark.xfail(reason="#3599") def test_anonymous_gets_giving(self): alice = self.make_participant('alice', last_bill_result='') - bob = self.make_participant('bob') - - alice.set_tip_to(bob, '1.00') - + Enterprise = self.make_team(is_approved=True) + alice.set_payment_instruction(Enterprise, '1.00') data = json.loads(self.client.GET('/~alice/public.json').body) - assert data['giving'] == '1.00' - @pytest.mark.xfail(reason="#3599") def test_anonymous_gets_null_giving_if_user_anonymous(self): - alice = self.make_participant( 'alice' - , last_bill_result='' - , anonymous_giving=True - ) - bob = self.make_participant('bob') - alice.set_tip_to(bob, '1.00') + alice = self.make_participant('alice', last_bill_result='', anonymous_giving=True) + Enterprise = self.make_team(is_approved=True) + alice.set_payment_instruction(Enterprise, '1.00') data = json.loads(self.client.GET('/~alice/public.json').body) - assert data['giving'] == None - @pytest.mark.xfail(reason="#3599") - def test_anonymous_gets_null_receiving_if_user_anonymous(self): - alice = self.make_participant( 'alice' - , last_bill_result='' - , anonymous_receiving=True - ) - bob = self.make_participant('bob') - alice.set_tip_to(bob, '1.00') - data = json.loads(self.client.GET('/~alice/public.json').body) - - assert data['receiving'] == None - - @pytest.mark.xfail(reason="#3599") - def test_authenticated_user_gets_their_tip(self): - alice = self.make_participant('alice', last_bill_result='') - bob = self.make_participant('bob') - - alice.set_tip_to(bob, '1.00') - - raw = self.client.GET('/~bob/public.json', auth_as='alice').body - - data = json.loads(raw) - - assert data['receiving'] == '1.00' - assert data['my_tip'] == '1.00' - - @pytest.mark.xfail(reason="#3599") - def test_authenticated_user_doesnt_get_other_peoples_tips(self): - alice = self.make_participant('alice', last_bill_result='') - bob = self.make_participant('bob', last_bill_result='') - carl = self.make_participant('carl', last_bill_result='') - dana = self.make_participant('dana') - - alice.set_tip_to(dana, '1.00') - bob.set_tip_to(dana, '3.00') - carl.set_tip_to(dana, '12.00') - - raw = self.client.GET('/~dana/public.json', auth_as='alice').body - - data = json.loads(raw) - - assert data['receiving'] == '16.00' - assert data['my_tip'] == '1.00' - - @pytest.mark.xfail(reason="#3599") - def test_authenticated_user_gets_zero_if_they_dont_tip(self): - self.make_participant('alice', last_bill_result='') - bob = self.make_participant('bob', last_bill_result='') - carl = self.make_participant('carl') - - bob.set_tip_to(carl, '3.00') - - raw = self.client.GET('/~carl/public.json', auth_as='alice').body - - data = json.loads(raw) - - assert data['receiving'] == '3.00' - assert data['my_tip'] == '0.00' - - @pytest.mark.xfail(reason="#3599") - def test_authenticated_user_gets_self_for_self(self): - alice = self.make_participant('alice', last_bill_result='') - bob = self.make_participant('bob') - - alice.set_tip_to(bob, '3.00') - - raw = self.client.GET('/~bob/public.json', auth_as='bob').body - - data = json.loads(raw) - - assert data['receiving'] == '3.00' - assert data['my_tip'] == 'self' - def test_access_control_allow_origin_header_is_asterisk(self): self.make_participant('alice', last_bill_result='') response = self.client.GET('/~alice/public.json') - assert response.headers['Access-Control-Allow-Origin'] == '*' - @pytest.mark.xfail(reason="#3599") def test_jsonp_works(self): alice = self.make_participant('alice', last_bill_result='') - bob = self.make_participant('bob') - - alice.set_tip_to(bob, '3.00') - - raw = self.client.GxT('/~bob/public.json?callback=foo', auth_as='bob').body - + Enterprise = self.make_team(is_approved=True) + alice.set_payment_instruction(Enterprise, '3.00') + picard = Participant.from_username('picard') + raw = self.client.GxT('/~picard/public.json?callback=foo', auth_as='picard').body assert raw == '''\ foo({ "avatar": null, @@ -160,15 +58,15 @@ def test_jsonp_works(self): "github": { "id": %(elsewhere_id)s, "user_id": "%(user_id)s", - "user_name": "bob" + "user_name": "picard" } }, "giving": "0.00", "id": %(user_id)s, - "my_tip": "self", - "npatrons": 1, + "ngiving_to": 0, + "ntaking_from": 1, "number": "singular", "on": "gratipay", - "receiving": "3.00", - "username": "bob" -})''' % dict(user_id=bob.id, elsewhere_id=bob.get_accounts_elsewhere()['github'].id) + "taking": "3.00", + "username": "picard" +})''' % dict(user_id=picard.id, elsewhere_id=picard.get_accounts_elsewhere()['github'].id) diff --git a/tests/py/test_teams.py b/tests/py/test_teams.py index 11daabb5e8..08a630c6f8 100644 --- a/tests/py/test_teams.py +++ b/tests/py/test_teams.py @@ -208,7 +208,7 @@ def test_migrate_tips_checks_for_multiple_teams(self): assert len(payment_instructions) == 1 - # cached values - receiving, ngivers + # cached values - receiving, nreceiving_from def test_receiving_only_includes_funded_payment_instructions(self): alice = self.make_participant('alice', claimed_time='now', last_bill_result='') @@ -219,7 +219,7 @@ def test_receiving_only_includes_funded_payment_instructions(self): bob.set_payment_instruction(team, '5.00') assert team.receiving == Decimal('3.00') - assert team.nsupporters == 1 + assert team.nreceiving_from == 1 funded_payment_instruction = self.db.one("SELECT * FROM payment_instructions " "WHERE is_funded ORDER BY id") @@ -233,4 +233,4 @@ def test_receiving_only_includes_latest_payment_instructions(self): alice.set_payment_instruction(team, '3.00') assert team.receiving == Decimal('3.00') - assert team.nsupporters == 1 + assert team.nreceiving_from == 1 diff --git a/www/~/%username/charts.json.spt b/www/%team/charts.json.spt similarity index 100% rename from www/~/%username/charts.json.spt rename to www/%team/charts.json.spt diff --git a/www/%team/index.html.spt b/www/%team/index.html.spt index 6c09ed5142..e2bb2daf34 100644 --- a/www/%team/index.html.spt +++ b/www/%team/index.html.spt @@ -27,6 +27,43 @@ banner = name = team.name {% endblock %} +{% block banner %} + +
+ {% if team.is_approved %} + + + + + + + + + + + + + + + + +
{{ _("Weekly") }}Σ  n
{{ _("Receives") }}{{ format_currency(team.receiving, 'USD') }}{{ team.nreceiving_from }}
{{ _("Shares") }}{{ format_currency(0, 'USD') }}0
+ {% endif %} + +

+ {% if team.is_approved %} + {{ _('Joined {ago}', ago=to_age(team.ctime, add_direction=True)) }} + {% else %} + {{ _('Applied {ago}', ago=to_age(team.ctime, add_direction=True)) }} + {% endif %} +

+ + +
+ +{{ super() }} +{% endblock %} + {% block sidebar %} {% if team.is_approved %} {% include "templates/your-payment.html" %} @@ -37,7 +74,7 @@ banner = name = team.name

{% if team.status == 'unreviewed' %} - {{ _("Unreviewed") }} | + {{ _("Under Review") }} | {% elif team.status == 'rejected' %} {{ _("Rejected") }} | {% endif %} @@ -53,7 +90,7 @@ banner = name = team.name {% endif %} | {{ _( "owned by {a}~{owner}{_a}" - , a=(''.format(team.owner))|safe + , a=(''.format(team.owner))|safe , owner=team.owner , _a=''|safe ) }} diff --git a/www/%team/payment-instruction.json.spt b/www/%team/payment-instruction.json.spt index 26cec607a5..acf98b7a3b 100644 --- a/www/%team/payment-instruction.json.spt +++ b/www/%team/payment-instruction.json.spt @@ -36,7 +36,7 @@ else: amount = out['amount'] total_giving = user.participant.giving - total_receiving = user.participant.receiving + total_taking = user.participant.taking out["amount"] = str(amount) out["amount_l"] = format_currency(amount, 'USD') @@ -45,17 +45,17 @@ else: out["msg"] += _("Thank you so much for supporting {0}!", team.name) else: out["msg"] = _("You have successfully canceled your payment to {0}.", team.name) - out["nsupporters"] = team.nsupporters + out["nreceiving_from"] = team.nreceiving_from out["team_id"] = team.id out["total_giving"] = str(total_giving) out["total_giving_l"] = format_currency(total_giving, 'USD') + out["total_taking"] = str(total_taking) + out["total_taking_l"] = format_currency(total_taking, 'USD') + + total_receiving = team.receiving out["total_receiving"] = str(total_receiving) out["total_receiving_l"] = format_currency(total_receiving, 'USD') - total_receiving_team = team.receiving - out["total_receiving_team"] = str(total_receiving_team) - out["total_receiving_team_l"] = format_currency(total_receiving_team, 'USD') - if 'ctime' in out: out["ctime"] = str(out['ctime']) out["mtime"] = str(out['mtime']) diff --git a/www/~/%username/receiving/index.html.spt b/www/%team/receiving/index.html.spt similarity index 85% rename from www/~/%username/receiving/index.html.spt rename to www/%team/receiving/index.html.spt index fc3f8b0eb0..5f8d62c9a9 100644 --- a/www/~/%username/receiving/index.html.spt +++ b/www/%team/receiving/index.html.spt @@ -1,13 +1,14 @@ +from aspen import Response from gratipay.utils import get_participant [-----------------------------------------------------------------------------] -participant = get_participant(state, restrict=False) -if participant.anonymous_receiving: - if user.participant != participant and not user.ADMIN: - website.redirect('../', base_url='') - -banner = '~' + participant.username +website.redirect('../', base_url='') +team = get_team(state) +if team.is_approved in (None, False): + if user.ANON: + raise Response(401) +banner = team.name title = _("Receiving") [-----------------------------------------------------------------------------] @@ -29,8 +30,8 @@ title = _("Receiving")

-

{{ _("Number of Patrons Per Week") }}

-
+

{{ _("Number of Payments Per Week") }}

+
{{ _("weeks") }}
diff --git a/www/about/pricing.spt b/www/about/pricing.spt index 981e35ae4c..1ddab45d1a 100644 --- a/www/about/pricing.spt +++ b/www/about/pricing.spt @@ -84,7 +84,7 @@ $(document).ready(function() { var btn = $('button.suggestion'); btn.click(function() { var amount = parseFloat($(this).attr('data-amount'), 10); - Gratipay.tips.set('Gratipay', amount, function(data) { + Gratipay.payments.set('Gratipay', amount, function(data) { $('.suggestion-prompt').slideUp(); $('button.suggestion').slideUp(); amount = parseFloat(data.amount, 10).toFixed(2); diff --git a/www/on/%platform/index.spt b/www/on/%platform/index.spt index d7e8310b09..fb3af976ac 100644 --- a/www/on/%platform/index.spt +++ b/www/on/%platform/index.spt @@ -93,18 +93,16 @@ limited = getattr(platform, 'api_friends_limited', False) {{ _('Name') }} {{ _('On Gratipay?') }} - {{ _('Supporters') }} - {{ _('Receives') }} {{ _('Gives') }} + {{ _('Takes') }} {% for friend in friends %} {% set p = friend.participant %} {{ friend.friendly_name }} {{ '✓'|safe if p.is_claimed }} - {{ _('hidden') if p.anonymous_receiving else p.npatrons }} - {{ _('hidden') if p.anonymous_receiving else p.receiving }} {{ _('hidden') if p.anonymous_giving else p.giving }} + {{ p.taking }} {% endfor %} {% if pages_urls %} diff --git a/www/on/confirm.html.spt b/www/on/confirm.html.spt index c7893fd2d5..4923f9f1d1 100644 --- a/www/on/confirm.html.spt +++ b/www/on/confirm.html.spt @@ -21,10 +21,10 @@ other_accounts = other.get_accounts_elsewhere().values() abandoned_account = user_accounts_dict.get(account.platform, None) giving_after = user.participant.giving -receiving_after = user.participant.receiving +taking_after = user.participant.taking if nix: giving_after += other.giving - receiving_after += other.receiving + taking_after += other.taking fmt = lambda x: format_currency(x, 'USD') get_name = lambda a: a.user_name or a.display_name or a.user_id @@ -52,7 +52,7 @@ suppress_sidebar = True
{{ user.participant.username }}
{{ _("Gives") }} {{ fmt(user.participant.giving) }}
- {{ _("Receives") }} {{ fmt(user.participant.receiving) }}
+ {{ _("Takes") }} {{ fmt(user.participant.taking) }}

{% for account in user_accounts %} @@ -65,7 +65,7 @@ suppress_sidebar = True
{{ other.username }}
{{ _("Gives") }} {{ fmt(other.giving) }}
- {{ _("Receives") }} {{ fmt(other.receiving) }}
+ {{ _("Takes") }} {{ fmt(other.taking) }}

{% for other_account in other_accounts %} {{ user.participant.username }}
{{ _("Gives") }} {{ fmt(giving_after) }}
- {{ _("Receives") }} {{ fmt(receiving_after) }}
+ {{ _("Takes") }} {{ fmt(taking_after) }}

{% for user_account in user_accounts %} @@ -113,7 +113,7 @@ suppress_sidebar = True {% endif %} {{ other.username }}
{{ _("Gives") }} {{ fmt(other.giving) }}
- {{ _("Receives") }} {{ fmt(other.receiving) }}
+ {{ _("Takes") }} {{ fmt(other.taking) }}

{% for other_account in other_accounts %} diff --git a/www/~/%username/number.json.spt b/www/~/%username/number.json.spt deleted file mode 100644 index fd71c24abf..0000000000 --- a/www/~/%username/number.json.spt +++ /dev/null @@ -1,38 +0,0 @@ -from __future__ import print_function, unicode_literals - -from aspen import Response -from gratipay.utils import get_participant -from gratipay.exceptions import ProblemChangingNumber - -[-----------------------------------------------------------------------------] - -request.allow("POST") -participant = get_participant(state, restrict=True) - -number = request.body["number"] - -if number not in ("singular", "plural"): - raise Response(400) - -out = {} - -if number != participant.number: - if number == "singular" and not request.body.get("confirmed") and participant.nmembers > 0: - msg = _("Warning: Doing this will remove all the members from your team.\n\n" - "You'll have to add them back manually to undo this!") - out = {'confirm': msg} - elif number == "plural" and not request.body.get("confirmed") and participant.anonymous_receiving == True: - msg = _("Warning: This will make the total amount you receive publicly visible.\n\n" - "Are you sure you want to do this?") - out = {'confirm': msg} - else: - try: - participant.update_number(number) - except ProblemChangingNumber, e: - raise Response(400, unicode(e)) - msg = _("Your account type has been changed.") - out = {'number': number, 'msg': msg} - - -[---] application/json via json_dump -out diff --git a/www/~/%username/privacy.json.spt b/www/~/%username/privacy.json.spt index 092a3d22ee..e946401f86 100644 --- a/www/~/%username/privacy.json.spt +++ b/www/~/%username/privacy.json.spt @@ -7,18 +7,15 @@ participant = get_participant(state, restrict=True) if request.method == 'POST': field = request.body.get("toggle") - if field not in ["is_searchable", "anonymous_receiving", "anonymous_giving"]: + if field not in ["is_searchable", "anonymous_giving"]: raise Response(400) - if participant.IS_PLURAL and field == "anonymous_receiving": - raise Response(403, _("Teams aren't allowed to hide their receiving amount.")) - rec = website.db.one(""" UPDATE participants SET {0}=not {0} WHERE username=%s - RETURNING {0} + RETURNING {0} """.format(field), (user.participant.username,)) assert rec is not None @@ -29,7 +26,7 @@ if request.method == 'POST': else: rec = website.db.one(""" - SELECT is_searchable, anonymous_receiving, anonymous_giving + SELECT is_searchable, anonymous_giving FROM participants WHERE username=%s """, (user.participant.username,), back_as=dict) diff --git a/www/~/%username/settings/close.spt b/www/~/%username/settings/close.spt index f4648793dc..f4d214f2e4 100644 --- a/www/~/%username/settings/close.spt +++ b/www/~/%username/settings/close.spt @@ -79,16 +79,13 @@ elif nteams == 1: href="https://github.com/gratipay/gratipay.com/issues/397">data retention policy).

-

Things we clear immediately include your profile statement, the - tips you're receiving, and those you're giving. You'll also be - removed from any communities and teams you were a part of. If - you're closing a team account, all team members will be removed - from the team.

- -

We specifically don't delete your past giving and - receiving history on the site, because that information also - belongs equally to other users (the ones you gave to and - received from).

+

Things we clear immediately include your profile statement, + the payroll you're taking, and the payments you're giving.

+ +

We specifically don't delete your past giving and + taking history on the site, because that information also belongs + equally to other users (the owners of the Teams you gave to and + took from).

After you close your account, your profile page will say, “The account owner has closed this account.”

diff --git a/www/~/%username/settings/index.html.spt b/www/~/%username/settings/index.html.spt index 18581e3e72..32c6a840a6 100644 --- a/www/~/%username/settings/index.html.spt +++ b/www/~/%username/settings/index.html.spt @@ -88,14 +88,6 @@ emails = participant.get_emails() {{ _("Hide total giving to others.") }}
- {% if participant.IS_SINGULAR %} - -
- {% endif %}