diff --git a/emails/donations_paused.spt b/emails/donations_paused.spt new file mode 100644 index 000000000..32da6b0bc --- /dev/null +++ b/emails/donations_paused.spt @@ -0,0 +1,4 @@ +{{ _("Liberapay donation paused") }} + +[---] text/html +

{{ _("{0} has temporarily paused your donations to them.", recipient) }}

diff --git a/emails/donations_resumed.spt b/emails/donations_resumed.spt new file mode 100644 index 000000000..885190784 --- /dev/null +++ b/emails/donations_resumed.spt @@ -0,0 +1,4 @@ +{{ _("Liberapay donation resumed") }} + +[---] text/html +

{{ _("{0} has resumed your donations to them.", recipient) }}

diff --git a/liberapay/models/participant.py b/liberapay/models/participant.py index 220e82d50..803c847ea 100644 --- a/liberapay/models/participant.py +++ b/liberapay/models/participant.py @@ -2235,16 +2235,19 @@ def update_goal(self, goal, cursor=None): raise UnexpectedCurrency(goal, self.main_currency) with self.db.get_cursor(cursor) as c: json = None if goal is None else str(goal) + donations_paused = goal == -2 and self.goal != -1 + donations_resumed = goal != -1 and self.goal == -2 self.add_event(c, 'set_goal', json) c.run("UPDATE participants SET goal=%s WHERE id=%s", (goal, self.id)) self.set_attributes(goal=goal) - if not self.accepts_tips: + if not self.accepts_tips or donations_resumed: tippers = c.all(""" SELECT p FROM current_tips t JOIN participants p ON p.id = t.tipper WHERE t.tippee = %s """, (self.id,)) + if not self.accepts_tips: for tipper in tippers: tipper.update_giving(cursor=c) r = c.one(""" @@ -2255,6 +2258,16 @@ def update_goal(self, goal, cursor=None): RETURNING receiving, npatrons """, (self.id,)) self.set_attributes(**r._asdict()) + if donations_paused or donations_resumed: + for tipper in tippers: + event = 'donations_paused' if donations_paused else 'donations_resumed' + tipper.send_email( + event, + tipper.get_email(tipper.get_email_address()), + recipient=self.username, + donations_paused=donations_paused, + donations_url=tipper.url('giving/') + ) def update_status(self, status, cursor=None): with self.db.get_cursor(cursor) as c: diff --git a/tests/py/test_goal.py b/tests/py/test_goal.py index 2f5f886fd..edf8b2f61 100644 --- a/tests/py/test_goal.py +++ b/tests/py/test_goal.py @@ -1,5 +1,5 @@ from liberapay.testing import EUR, Harness - +from liberapay.testing.emails import EmailHarness class Tests(Harness): @@ -70,3 +70,35 @@ def test_team_member_can_change_team_goal(self): ) assert r.code == 302 assert team.refetch().goal == EUR('99.99') + +class TestPauseDonations(EmailHarness): + def setUp(self): + EmailHarness.setUp(self) + self.alice = self.make_participant('alice', email='alice@example.com') + + def change_goal(self, goal, goal_custom="", auth_as="alice", expect_success=False): + r = self.client.PxST( + "/alice/edit/goal", + {'goal': goal, 'goal_custom': goal_custom}, + auth_as=self.alice if auth_as == 'alice' else auth_as + ) + if expect_success and r.code >= 400: + raise r + return r + + def test_pause(self): + bob = self.make_participant('bob', email='bob@example.com') + bob.set_tip_to(self.alice, EUR('0.99'), renewal_mode=2) + self.change_goal("-2", expect_success=True) + + assert self.mailer.call_count == 1 + last_email = self.get_last_email() + assert last_email['to'][0] == 'bob ' + assert "paused" in last_email['text'] + + self.change_goal("null", "", expect_success=True) + + assert self.mailer.call_count == 2 + last_email = self.get_last_email() + assert last_email['to'][0] == 'bob ' + assert "resume" in last_email['text'] diff --git a/www/%username/edit/goal.spt b/www/%username/edit/goal.spt index 4f19edb4e..23080a37e 100644 --- a/www/%username/edit/goal.spt +++ b/www/%username/edit/goal.spt @@ -11,22 +11,25 @@ if request.method == "POST": goal = request.body["goal_custom"] goal_currency = request.body.get_currency('currency', participant.main_currency) goal = locale.parse_money_amount(goal, goal_currency) - if not (goal > 0 or participant.is_person and goal.amount in (0, -1)): + if not (goal > 0 or participant.is_person and goal.amount in (0, -1, -2)): raise response.invalid_input(goal, 'goal_custom', 'body') else: - goal = Money(request.body.get_int("goal", minimum=-1), participant.main_currency).round_down() - if not (goal > 0 or participant.is_person and goal.amount in (0, -1)): + goal = Money(request.body.get_int("goal", minimum=-2), participant.main_currency).round_down() + if not (goal > 0 or participant.is_person and goal.amount in (0, -1, -2)): raise response.invalid_input(goal, 'goal', 'body') - participant.update_goal(goal) + if goal != participant.goal: + participant.update_goal(goal) form_post_success(state) if participant.kind == 'individual': GRATEFUL = _("I'm grateful for gifts, but don't have a specific funding goal.") + GRATEFUL_PAUSED_GIFTS = _("I'm grateful for gifts, but they are currently paused.") PATRON = _("I'm here as a patron.") PATRON_NO_GIFTS = _("I'm here as a patron, and politely decline to receive gifts.") GOAL_RAW = _("My goal is to receive {0}") else: GRATEFUL = _("We're grateful for gifts, but don't have a specific funding goal.") + GRATEFUL_PAUSED_GIFTS = _("We're grateful for gifts, but they are currently paused.") PATRON = _("We're here as a patron.") PATRON_NO_GIFTS = _("We're here as a patron, and politely decline to receive gifts.") GOAL_RAW = _("Our goal is to receive {0}") @@ -69,6 +72,12 @@ subhead = _("Goal")
% if participant.kind != 'group' +
+
% endif diff --git a/www/%username/giving/index.html.spt b/www/%username/giving/index.html.spt index 6d376cb4e..62d5c32cf 100644 --- a/www/%username/giving/index.html.spt +++ b/www/%username/giving/index.html.spt @@ -357,6 +357,10 @@ next_payday = compute_next_payday_date()

{{ glyphicon('warning-sign') }} {{ _( "Inactive because the account of the recipient is closed." ) }}

+ % elif tippee.goal == -2 +

{{ glyphicon('pause') }} {{ _( + "This donation has been paused by the recipient." + ) }}

% elif not tippee.accepts_tips

{{ glyphicon('warning-sign') }} {{ _( "Inactive because the recipient no longer accepts donations."