Skip to content

Commit

Permalink
Merge pull request #2283 from liberapay/various
Browse files Browse the repository at this point in the history
  • Loading branch information
Changaco committed Oct 13, 2023
2 parents 12296db + d425241 commit aee64f4
Show file tree
Hide file tree
Showing 19 changed files with 413 additions and 76 deletions.
1 change: 1 addition & 0 deletions js/tail_log.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Liberapay.stream_lines = function(url, data_cb, error_cb) {
function fetch_lines(first_pos) {
jQuery.ajax({
url: url,
dataType: 'text',
headers: {Range: 'x-lines='+first_pos+'-'},
}).done(function(data, textStatus, xhr) {
var file_is_partial = false;
Expand Down
27 changes: 17 additions & 10 deletions liberapay/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -640,19 +640,26 @@ def check_address_v2(addr):
return True


def render_postal_address(addr, single_line=False):
def render_postal_address(addr, single_line=False, format='local'):
if not check_address_v2(addr):
return
# FIXME The rendering below is simplistic, we should implement
# https://github.com/liberapay/liberapay.com/issues/1056
elements = [addr['local_address'], addr['city'], addr['postal_code']]
if addr.get('region'):
elements.append(addr['region'])
elements.append(LOCALE_EN.countries[addr['country']])
if single_line:
return ', '.join(elements)
if format == 'local':
# FIXME The rendering below is simplistic, we should implement
# https://github.com/liberapay/liberapay.com/issues/1056
elements = [addr['local_address'], addr['city'], addr['postal_code']]
if addr.get('region'):
elements.append(addr['region'])
elements.append(LOCALE_EN.countries[addr['country']])
sep = ', ' if single_line else '\n'
elif format == 'downward':
elements = [LOCALE_EN.countries[addr['country']]]
if addr.get('region'):
elements.append(addr['region'])
elements += [addr['city'], addr['postal_code'], addr['local_address']]
sep = ' / ' if single_line else '\n'
else:
return '\n'.join(elements)
raise ValueError(f"unknown `format` value {format!r}")
return sep.join(elements)


def mkdir_p(path):
Expand Down
16 changes: 15 additions & 1 deletion liberapay/wireup.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
from liberapay.utils import find_files, markdown, resolve
from liberapay.utils.emails import compile_email_spt
from liberapay.utils.http_caching import asset_etag
from liberapay.utils.types import Object
from liberapay.utils.types import LocalizedString, Object
from liberapay.version import get_version
from liberapay.website import Website

Expand Down Expand Up @@ -172,6 +172,20 @@ def cast_currency_basket(v, cursor):
except (psycopg2.ProgrammingError, NeedDatabase):
pass

def cast_localized_string(v, cursor):
if v in (None, '(,)'):
return None
else:
text, lang = v[1:-1].split(',')
if text.startswith('"') and text.endswith('"'):
text = text[1:-1].replace('""', '"')
return LocalizedString(text, lang)
try:
oid = db.one("SELECT 'localized_string'::regtype::oid")
register_type(new_type((oid,), 'localized_string', cast_localized_string))
except (psycopg2.ProgrammingError, NeedDatabase):
pass

if db and env.override_query_cache:
db.cache.max_size = 0

Expand Down
1 change: 1 addition & 0 deletions sql/branch.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE TYPE localized_string AS (string text, lang text);
12 changes: 12 additions & 0 deletions style/base/utils.scss
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@
margin-top: 0 !important;
}

.m-0 {
margin: 0;
}

.mb-3 {
margin-bottom: ($line-height-computed / 2);
}
Expand All @@ -47,6 +51,14 @@
margin-bottom: $line-height-computed;
}

.row-gap-3 {
row-gap: 0.5em;
}

.column-gap-2 {
column-gap: 0.3em;
}

.pre-wrap {
white-space: pre-wrap;
}
Expand Down
5 changes: 3 additions & 2 deletions templates/macros/pagination.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@
% if current_page > 1 or has_more
<ul class="pager">
% if current_page > 1
<li class="previous"><a href="{{ request.path.raw if current_page == 2 else '?page=%i' % (current_page - 1) }}">
% set prev_page = None if current_page == 2 else current_page - 1
<li class="previous"><a href="{{ request.qs.derive(page=prev_page) }}">
← {{ _("Previous") }}
</a></li>
% endif
% if has_more
<li class="next"><a href="?page={{ current_page + 1 }}">
<li class="next"><a href="{{ request.qs.derive(page=current_page + 1) }}">
{{ _("View More") if current_page == 1 else _("Next") }} →
</a></li>
% endif
Expand Down
10 changes: 6 additions & 4 deletions templates/macros/profile-box.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,20 @@
</div>
% endmacro

% macro profile_box_embedded(participant, summary, nmembers=None)
% macro profile_box_embedded(participant, summary, nmembers=None, numbers=True)
% call profile_box_embedded_wrapper(participant, participant.path(''))
{{ profile_box_embedded_participant(participant, summary, nmembers=nmembers) }}
{{ profile_box_embedded_participant(participant, summary, nmembers=nmembers, numbers=numbers) }}
% endcall
% endmacro

% macro profile_box_embedded_participant(participant, summary, nmembers=None)
% macro profile_box_embedded_participant(participant, summary, nmembers=None, numbers=True)
% set username = participant.username

<h4><a href="/{{ username }}/">{{ username }}</a></h4>

<p class="summary">{{ summary or '' }}</p>
<p class="summary" lang="{{ summary.lang|default('') }}">{{ summary or '' }}</p>

% if numbers
<div class="numbers">
<dl>
<dt>{{ _("Patrons") }}</dt>
Expand All @@ -47,6 +48,7 @@ <h4><a href="/{{ username }}/">{{ username }}</a></h4>
</dl>
% endif
</div>
% endif
% endmacro

% macro profile_box_embedded_elsewhere(
Expand Down
22 changes: 21 additions & 1 deletion tests/py/test_payday.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from liberapay.constants import EVENTS
from liberapay.i18n.currencies import MoneyBasket
from liberapay.models.participant import Participant
from liberapay.testing import EUR, JPY, USD, Foobar
from liberapay.testing import EUR, JPY, USD, Foobar, website
from liberapay.testing.emails import EmailHarness


Expand Down Expand Up @@ -57,6 +57,26 @@ def test_payday_can_be_restarted_after_crash(self, transfer_for_real, exec_payda
transfer_for_real.side_effect = None
Payday.start().run()

def test_payday_log_can_be_accessed(self):
_override_payday_checks = website.env.override_payday_checks
website.env.override_payday_checks = True
try:
alice = self.make_participant('alice', privileges=1)
r = self.client.PxST('/admin/payday', data={'action': 'run_payday'}, auth_as=alice)
assert r.code == 302
assert r.headers[b'Location'] == b'/admin/payday/1'
finally:
website.env.override_payday_checks = _override_payday_checks
r = self.client.GET('/admin/payday/1', auth_as=alice)
assert r.code == 200
r = self.client.GxT(
'/admin/payday/1.txt',
HTTP_RANGE=b'x-lines=0-', HTTP_ACCEPT=b'text/plain',
auth_as=alice,
)
assert r.code == 206
assert r.headers[b'Content-Type'] == b'text/plain'

def test_payday_id_is_serial(self):
for i in range(1, 4):
self.db.run("SELECT nextval('paydays_id_seq')")
Expand Down
3 changes: 2 additions & 1 deletion www/%username/settings/close.spt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ if request.method == 'POST':
feedback = request.body.get('feedback')
participant.store_feedback(feedback)
participant.close()
participant.sign_out(response.headers.cookie)
if user == participant:
user.sign_out(response.headers.cookie)
response.redirect('/%s/' % participant.username)

title = _("Close Account")
Expand Down
18 changes: 17 additions & 1 deletion www/admin/payments.spt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from liberapay.i18n.base import LOCALE_EN as locale
from liberapay.utils import render_postal_address

MARKS_MAP = {
'trusted': 'success',
Expand Down Expand Up @@ -27,6 +28,13 @@ before = request.qs.get_int('before', default=None)
status = request.qs.get('status')
payins = website.db.all("""
SELECT pi.*, p.username AS payer_name, p.marked_as AS payer_marked_as
, ( SELECT i.info
FROM identities i
WHERE i.participant = pi.payer
AND pi.status = 'awaiting_review'
ORDER BY i.ctime DESC
LIMIT 1
) AS payer_identity
, r.network AS payin_method
, ( SELECT json_agg((SELECT x FROM ( SELECT
pt.recipient, recipient.username AS recipient_name,
Expand Down Expand Up @@ -92,7 +100,15 @@ title = "Payments Admin"
<tr>
<td>{{ pi.id }}</td>
<td>{{ pi.ctime.replace(microsecond=0, tzinfo=None) }}</td>
<td><a href="/~{{ pi.payer }}/">{{ pi.payer_name }}</a>{% if pi.payer_marked_as %}<br>
<td><a href="/~{{ pi.payer }}/">{{ pi.payer_name }}</a>{% if pi.payer_identity %}<br>
% set payer_identity = pi.payer_identity.decrypt()
<span class="break-word-anywhere monospace">
{{- payer_identity.name }}<br>
{{- render_postal_address(
payer_identity.postal_address, single_line=True, format='downward'
) -}}
</span>
{% endif %}{% if pi.payer_marked_as %}<br>
<span class="text-{{ MARKS_MAP[pi.payer_marked_as] }}">[{{
pi.payer_marked_as
}}]</span>{% endif %}</td>
Expand Down
2 changes: 1 addition & 1 deletion www/explore/elsewhere/%platform.spt
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ if platform:
def get_participants(random):
return website.db.all("""
SELECT p
, ( SELECT s.content
, ( SELECT (s.content, s.lang)::localized_string
FROM statements s
WHERE s.participant = p.id
AND s.type = 'summary'
Expand Down
74 changes: 74 additions & 0 deletions www/explore/hopefuls.spt
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
[---]

per_page = 18
last_shown = request.qs.get_int('last_shown', default=None)
order = request.qs.get_choice('order', ('asc', 'desc'), default='desc')
op = '<' if order == 'desc' else '>'
participants = website.db.all("""
SELECT p
, ( SELECT (s.content, s.lang)::localized_string
FROM statements s
WHERE s.participant = p.id
AND s.type = 'summary'
ORDER BY s.lang = %s DESC, s.id
LIMIT 1
) AS summary
FROM participants p
WHERE p.kind IN ('individual', 'organization', 'group')
AND p.status = 'active'
AND (p.goal > 0 OR p.goal IS NULL)
AND p.hide_from_lists = 0
AND p.receiving = 0
AND p.payment_providers > 0
AND p.join_time < (current_timestamp - interval '72 hours')
AND coalesce(p.id {} %s, true)
ORDER BY p.id {}
LIMIT %s
""".format(op, order), (locale.language, last_shown, per_page + 1), max_age=0)
has_more = len(participants) > per_page
participants = participants[:per_page]

title = _("Explore")
subhead = _("Hopefuls")

[---] text/html
% from 'templates/macros/icons.html' import glyphicon with context
% from 'templates/macros/pagination.html' import simple_pager with context
% from 'templates/macros/profile-box.html' import profile_box_embedded with context

% extends "templates/layouts/explore.html"

% block content

% if participants
<p>{{ _("This page lists Liberapay users who are hoping to receive their first donations.") }}</p>
<p class="text-warning">{{ glyphicon('warning-sign') }} {{ _(
"Despite our efforts, some of the listed profiles may be spam or fraud."
) }}</p>
% if last_shown is None
<p class="text-info">{{ glyphicon('info-sign') }} {{ _(
"Profiles only start appearing in the list 72 hours after they're created."
) }}</p>
% endif

<div class="inline-boxes">
% for p, summary in participants
{{ profile_box_embedded(p, summary, numbers=False) }}
% endfor
</div>
% if has_more
<ul class="pager">
<li class="next"><a href="{{ request.qs.derive(last_shown=participants[-1][0].id) }}">{{ _(
"Next Page →"
) }}</a></li>
</ul>
% endif
% else
<p>{{ _("Nothing to show.") }}</p>
% endif

% if user.ANON
<p><a class="btn btn-success btn-lg" href="/sign-up">{{ _("Create your account") }}</a></p>
% endif

% endblock
38 changes: 29 additions & 9 deletions www/explore/individuals.spt
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
[---]

sort_by = request.qs.get_choice('sort_by', ('receiving', 'join_time'), default='receiving')
order = request.qs.get_choice('order', ('asc', 'desc'), default='desc')
if sort_by == 'receiving':
sql_order = f"convert(p.receiving, 'EUR') {order}, p.join_time {order}"
else:
sql_order = f"p.{sort_by} {order}"
per_page = 18
current_page = request.qs.get_int('page', default=1, minimum=1, maximum=100)
individuals = website.db.all("""
SELECT p
, ( SELECT s.content
, ( SELECT (s.content, s.lang)::localized_string
FROM statements s
WHERE s.participant = p.id
AND s.type = 'summary'
Expand All @@ -18,10 +24,10 @@ individuals = website.db.all("""
AND p.hide_receiving IS NOT TRUE
AND p.hide_from_lists = 0
AND p.receiving > 0
ORDER BY convert(p.receiving, 'EUR') DESC, p.join_time DESC
ORDER BY {}
LIMIT %s
OFFSET %s
""", (locale.language, per_page + 1, (current_page - 1) * per_page))
""".format(sql_order), (locale.language, per_page + 1, (current_page - 1) * per_page), max_age=0)
has_more = len(individuals) > per_page
individuals = individuals[:per_page]

Expand All @@ -37,20 +43,34 @@ subhead = _("Individuals")
% block content

% if individuals
% if current_page == 1
% if current_page == 1 and sort_by == 'receiving' and order == 'desc'
<p>{{ _("The top {0} individuals on Liberapay are:", len(individuals)) }}</p>
% else
<p>{{ _(
"List of individuals on Liberapay, page {number}:", number=current_page
) }}</p>
% endif

<div class="inline-boxes">
% for p, summary in individuals
{{ profile_box_embedded(p, summary) }}
% endfor
</div>

{{ simple_pager(current_page, has_more) }}
<br>

<form action="" class="flex-row wrap align-items-center row-gap-3 column-gap-2 mb-4" method="GET">
<label class="m-0" for="sort_by">{{ _("Sort by") }}</label>
<div>
<select class="form-control" name="sort_by" id="sort_by">
<option value="receiving" {{ 'selected' if sort_by == 'receiving' }}>{{ _("income") }}</option>
<option value="join_time" {{ 'selected' if sort_by == 'join_time' }}>{{ _("creation date") }}</option>
</select>
</div>
<div>
<select class="form-control" name="order" aria-label="{{ _('sort order') }}">
<option value="desc" {{ 'selected' if order == 'desc' }}>{{ _("in descending order") }}</option>
<option value="asc" {{ 'selected' if order == 'asc' }}>{{ _("in ascending order") }}</option>
</select>
</div>
<button class="btn btn-default">{{ _("Go") }}</button>
</form>
% else
<p>{{ _("Nothing to show.") }}</p>
% endif
Expand Down
Loading

0 comments on commit aee64f4

Please sign in to comment.