diff --git a/liberapay/exceptions.py b/liberapay/exceptions.py index 6b09ab965..0770e089f 100644 --- a/liberapay/exceptions.py +++ b/liberapay/exceptions.py @@ -464,6 +464,19 @@ def msg(self, _, locale): ) +class UnacceptedDonationVisibility(LazyResponseXXX): + code = 403 + def msg(self, _): + tippee, visibility = self.args + return _( + "{username} no longer accepts secret donations.", username=tippee.username, + ) if visibility == 1 else _( + "{username} no longer accepts private donations.", username=tippee.username, + ) if visibility == 2 else _( + "{username} no longer accepts public donations.", username=tippee.username, + ) + + class UnexpectedCurrency(LazyResponse400): def __init__(self, unexpected_amount, expected_currency): diff --git a/liberapay/models/participant.py b/liberapay/models/participant.py index 1b1961ac4..f62b84e26 100644 --- a/liberapay/models/participant.py +++ b/liberapay/models/participant.py @@ -62,6 +62,7 @@ TooManyRequests, TooManyUsernameChanges, UnableToSendEmail, + UnacceptedDonationVisibility, UnexpectedCurrency, UsernameAlreadyTaken, UsernameBeginsWithRestrictedCharacter, @@ -2507,6 +2508,8 @@ def set_tip_to(self, tippee, periodic_amount, period='weekly', renewal_mode=None raise BadAmount(periodic_amount, period, limits) if amount.currency not in tippee.accepted_currencies_set: raise BadDonationCurrency(tippee, amount.currency) + if visibility and not tippee.accepts_tip_visibility(visibility): + raise UnacceptedDonationVisibility(tippee, visibility) # Insert tip t = self.db.one("""\ @@ -2616,6 +2619,11 @@ def hide_tip_to(self, tippee_id, hide=True): """, dict(tipper=self.id, tippee=tippee_id, hide=hide)) + def accepts_tip_visibility(self, visibility): + bit = 2 ** (visibility - 1) + return self.recipient_settings.patron_visibilities & bit > 0 + + @cached_property def donor_category(self): if self.is_suspended is False: diff --git a/sql/branch.sql b/sql/branch.sql new file mode 100644 index 000000000..db37d6735 --- /dev/null +++ b/sql/branch.sql @@ -0,0 +1,34 @@ +SELECT 'after deployment'; +BEGIN; + CREATE TEMPORARY TABLE _tippees ON COMMIT DROP AS ( + SELECT e.participant AS id + , (CASE WHEN e.payload->>'patron_visibilities' = '2' THEN 2 ELSE 3 END) AS only_accepted_visibility + , e.ts AS start_time + , coalesce(( + SELECT e2.ts + FROM events e2 + WHERE e2.participant = e.participant + AND e2.type = 'recipient_settings' + AND e2.ts > e.ts + ORDER BY e2.ts + LIMIT 1 + ), current_timestamp) AS end_time + FROM events e + WHERE e.type = 'recipient_settings' + AND e.payload->>'patron_visibilities' IN ('2', '4') + ); + UPDATE tips AS tip + SET visibility = tippee.only_accepted_visibility + FROM _tippees AS tippee + WHERE tip.tippee = tippee.id + AND tip.mtime > tippee.start_time + AND tip.mtime < tippee.end_time + AND tip.visibility <> tippee.only_accepted_visibility; + UPDATE payin_transfers AS pt + SET visibility = tippee.only_accepted_visibility + FROM _tippees AS tippee + WHERE pt.recipient = tippee.id + AND pt.ctime > tippee.start_time + AND pt.ctime < tippee.end_time + AND pt.visibility <> tippee.only_accepted_visibility; +ROLLBACK; diff --git a/templates/macros/your-tip.html b/templates/macros/your-tip.html index ec6a157b4..81759184d 100644 --- a/templates/macros/your-tip.html +++ b/templates/macros/your-tip.html @@ -235,16 +235,19 @@
{{ icon('info-sign') }} {{ _( "{username} has chosen not to see who their patrons are, so your donation will be secret.", username=tippee_name ) }}
% elif patron_visibilities == 2 or paypal_only and not patron_visibilities +{{ icon('info-sign') }} {{ _( "This donation won't be secret, you will appear in {username}'s private list of patrons.", username=tippee_name ) }}
% elif patron_visibilities == 4 +{{ icon('info-sign') }} {{ _( "{username} discloses who their patrons are, your donation will be public.", username=tippee_name @@ -299,6 +302,7 @@
{{ icon('info-sign') }} {{ _( "{username} hasn't yet specified whether they want to see who their patrons are, " "so your donation will be secret.", diff --git a/tests/py/test_donating.py b/tests/py/test_donating.py index 0c8bf5ef1..1053c489b 100644 --- a/tests/py/test_donating.py +++ b/tests/py/test_donating.py @@ -7,6 +7,7 @@ def test_donation_form_v2(self): creator = self.make_participant( 'creator', accepted_currencies=None, email='creator@liberapay.com', ) + creator.update_recipient_settings(patron_visibilities=7) r = self.client.GET('/creator/donate?currency=KRW') assert r.code == 200 assert ">Pledge<" in r.text @@ -54,6 +55,7 @@ def test_donation_form_v2_does_not_overwrite_visibility(self): creator = self.make_participant( 'creator', accepted_currencies=None, email='creator@liberapay.com', ) + creator.update_recipient_settings(patron_visibilities=7) self.add_payment_account(creator, 'stripe') donor = self.make_participant('donor') donor.set_tip_to(creator, USD('10.00'), renewal_mode=1, visibility=3) diff --git a/tests/py/test_settings.py b/tests/py/test_settings.py index 5eac73bcf..fbe808d2b 100644 --- a/tests/py/test_settings.py +++ b/tests/py/test_settings.py @@ -199,7 +199,7 @@ def test_enabling_and_disabling_non_secret_donations(self): # Check that the donation form isn't proposing the visibility options r = self.client.GET('/alice/donate') assert r.code == 200 - assert 'name="visibility"' not in r.text + assert 'name="visibility"' in r.text assert 'Secret donation' not in r.text assert 'Private donation' not in r.text assert 'Public donation' not in r.text @@ -221,7 +221,7 @@ def test_enabling_and_disabling_non_secret_donations(self): assert alice.recipient_settings.patron_visibilities == 1 r = self.client.GET('/alice/donate') assert r.code == 200 - assert 'name="visibility"' not in r.text + assert 'name="visibility"' in r.text assert 'Secret donation' not in r.text assert 'Private donation' not in r.text assert 'Public donation' not in r.text