Skip to content

Commit a67cedf

Browse files
authored
Merge pull request #6014 from internetarchive/trending
Adding trending view
2 parents 6610a1b + 35173b6 commit a67cedf

File tree

6 files changed

+130
-14
lines changed

6 files changed

+130
-14
lines changed

openlibrary/core/bookshelves.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
from typing import Literal
22
from openlibrary.utils.dateutil import DATE_ONE_MONTH_AGO, DATE_ONE_WEEK_AGO
33

4+
import logging
45
from . import db
56

7+
logger = logging.getLogger(__name__)
8+
69

710
class Bookshelves:
811

@@ -70,22 +73,26 @@ def total_unique_users(cls, since=None):
7073
return results[0] if results else None
7174

7275
@classmethod
73-
def most_logged_books(cls, shelf_id, limit=10, since=False):
76+
def most_logged_books(cls, shelf_id=None, limit=10, since=False):
7477
"""Returns a ranked list of work OLIDs (in the form of an integer --
7578
i.e. OL123W would be 123) which have been most logged by
7679
users. This query is limited to a specific shelf_id (e.g. 1
7780
for "Want to Read").
7881
"""
7982
oldb = db.get_db()
80-
query = 'select work_id, count(*) as cnt from bookshelves_books WHERE bookshelf_id=$shelf_id '
83+
where = 'WHERE bookshelf_id' + ('=$shelf_id' if shelf_id else ' IS NOT NULL ')
8184
if since:
82-
query += " AND created >= $since"
85+
where += 'AND created >= $since'
86+
query = f'select work_id, count(*) as cnt from bookshelves_books {where}'
8387
query += ' group by work_id order by cnt desc limit $limit'
84-
return list(
88+
logger.info("Query: %s", query)
89+
logged_books = list(
8590
oldb.query(
8691
query, vars={'shelf_id': shelf_id, 'limit': limit, 'since': since}
8792
)
8893
)
94+
logger.info("Results: %s", logged_books)
95+
return logged_books
8996

9097
@classmethod
9198
def count_total_books_logged_by_user(cls, username, bookshelf_ids=None):
@@ -167,12 +174,30 @@ def get_users_logged_books(
167174
"LIMIT $limit OFFSET $offset"
168175
)
169176
if bookshelf_id is None:
170-
query = "SELECT * from bookshelves_books WHERE " "username=$username"
177+
query = "SELECT * from bookshelves_books WHERE username=$username"
171178
# XXX Removing limit, offset, etc from data looks like a bug
172179
# unrelated / not fixing in this PR.
173180
data = {'username': username}
174181
return list(oldb.query(query, vars=data))
175182

183+
184+
@classmethod
185+
def get_recently_logged_books(cls, bookshelf_id=None, limit=50, page=1):
186+
oldb = db.get_db()
187+
page = int(page) if page else 1
188+
data = {
189+
'bookshelf_id': bookshelf_id,
190+
'limit': limit,
191+
'offset': limit * (page - 1),
192+
}
193+
where = "WHERE bookshelf_id=$bookshelf_id " if bookshelf_id else ""
194+
query = (
195+
f"SELECT * from bookshelves_books {where} "
196+
"ORDER BY created DESC LIMIT $limit OFFSET $offset"
197+
)
198+
return list(oldb.query(query, vars=data))
199+
200+
176201
@classmethod
177202
def get_users_read_status_of_work(cls, username, work_id):
178203
"""A user can mark a book as (1) want to read, (2) currently reading,

openlibrary/templates/stats/readinglog.html

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,19 @@ <h2>$_('# Books Starred')</h2>
5959
</div>
6060

6161
<div class="admin-section">
62-
<h3>$_('Most Wanted Books (All Time)')</h3>
62+
63+
<h3>$_('Most Wanted Books (This Month)')</h3>
6364
<div class="mybooks-list">
64-
<ul class="list-books">
65-
$for book in stats['leaderboard']['most_wanted_all']:
65+
<ul class="list-books">
66+
$for book in stats['leaderboard']['most_wanted_month']:
6667
$:macros.SearchResultsWork(book['work'], decorations=ungettext("Added 1 time", "Added %(count)d times", book['cnt'], count=book['cnt']), availability=book.get('availability'))
6768
</ul>
6869
</div>
6970

70-
<h3>$_('Most Wanted Books (This Month)')</h3>
71+
<h3>$_('Most Wanted Books (All Time)')</h3>
7172
<div class="mybooks-list">
7273
<ul class="list-books">
73-
$for book in stats['leaderboard']['most_wanted_month']:
74+
$for book in stats['leaderboard']['most_wanted_all']:
7475
$:macros.SearchResultsWork(book['work'], decorations=ungettext("Added 1 time", "Added %(count)d times", book['cnt'], count=book['cnt']), availability=book.get('availability'))
7576
</ul>
7677
</div>

openlibrary/templates/trending.html

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
$def with (logged_books=None, mode='now')
2+
3+
<div id="contentBody">
4+
$ pages = {'now': 'Now', 'daily': 'Today', 'weekly': 'This Week', 'monthly': 'This Month', 'yearly': 'This Year', 'forever': 'All Time'}
5+
<h1>$_('Trending Books'): $_(pages[mode])</h1>
6+
<p>$_("See what readers from the community are adding to their bookshelves")</p>
7+
<p>
8+
$for p in pages:
9+
<a style="$('font-weight: bold;' if p==mode else '')" href="/trending/$(p)">$pages[p]</a>
10+
</p>
11+
12+
<div class="mybooks-list">
13+
<ul class="list-books">
14+
$if logged_books:
15+
$for entry in logged_books:
16+
$if 'bookshelf_id' in entry:
17+
$ shelf = {1: "Want to Read", 2: "Currently Reading", 3: "Read"}[entry['bookshelf_id']]
18+
$ extra = "Someone marked as " + shelf + ", " + entry['created'].strftime("%m/%d/%Y at %H:%M:%S")
19+
$else:
20+
$ extra = 'Logged %i times %s' % (entry['cnt'], pages[mode])
21+
$if entry.get('work'):
22+
$:macros.SearchResultsWork(entry['work'], extra=extra, availability=entry['work'].get('availability'))
23+
</ul>
24+
</div>
25+
</div>

openlibrary/utils/dateutil.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@ def date_n_days_ago(n=None, start=None):
3030
return (_start - datetime.timedelta(days=n)) if n else None
3131

3232

33+
DATE_ONE_YEAR_AGO = date_n_days_ago(n=365)
3334
DATE_ONE_MONTH_AGO = date_n_days_ago(n=days_in_current_month())
3435
DATE_ONE_WEEK_AGO = date_n_days_ago(n=7)
36+
DATE_ONE_DAY_AGO = date_n_days_ago(n=1)
3537

3638

3739
def parse_date(datestr):

openlibrary/views/loanstats.py

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,27 @@ def reading_log_summary():
3232
reading_log_summary, 'stats.readling_log_summary', timeout=dateutil.HOUR_SECS
3333
)
3434

35+
def cached_get_most_logged_books(shelf_id=None, since_days=1, limit=20):
36+
return cache.memcache_memoize(
37+
get_most_logged_books, 'stats.trending', timeout=dateutil.HOUR_SECS
38+
)(shelf_id=shelf_id, since_days=since_days, limit=limit)
39+
40+
def get_most_logged_books(shelf_id=None, since_days=1, limit=20):
41+
"""
42+
shelf_id: Bookshelves.PRESET_BOOKSHELVES['Want to Read'|'Already Read'|'Currently Reading']
43+
since: DATE_ONE_YEAR_AGO, DATE_ONE_MONTH_AGO, DATE_ONE_WEEK_AGO, DATE_ONE_DAY_AGO
44+
"""
45+
# enable to work w/ cached
46+
if 'env' not in web.ctx:
47+
delegate.fakeload()
48+
49+
# Return as dict to enable cache serialization
50+
return [dict(book) for book in
51+
Bookshelves.most_logged_books(
52+
shelf_id=shelf_id,
53+
since=dateutil.date_n_days_ago(since_days),
54+
limit=limit)]
55+
3556

3657
def reading_log_leaderboard(limit=None):
3758
# enable to work w/ cached
@@ -72,7 +93,6 @@ def get_cached_reading_log_stats(limit):
7293
stats.update(cached_reading_log_leaderboard(limit))
7394
return stats
7495

75-
7696
class stats(app.view):
7797
path = "/stats"
7898

@@ -88,14 +108,57 @@ class lending_stats(app.view):
88108
def GET(self, key, value):
89109
raise web.seeother("/")
90110

111+
def get_activity_stream(limit=None):
112+
# enable to work w/ cached
113+
if 'env' not in web.ctx:
114+
delegate.fakeload()
115+
return Bookshelves.get_recently_logged_books(limit=limit)
116+
117+
def get_cached_activity_stream(limit):
118+
return cache.memcache_memoize(
119+
get_activity_stream,
120+
'stats.activity_stream',
121+
timeout=dateutil.HOUR_SECS,
122+
)(limit)
123+
124+
class activity_stream(app.view):
125+
path = "/trending(/?.*)"
126+
127+
def GET(self, page=''):
128+
if not page:
129+
raise web.seeother("/trending/now")
130+
page = page[1:]
131+
limit = 20
132+
if page == "now":
133+
logged_books = get_activity_stream(limit=limit)
134+
else:
135+
shelf_id = None # optional; get from web.input()?
136+
logged_books = cached_get_most_logged_books(since_days={
137+
'daily': 1,
138+
'weekly': 7,
139+
'monthly': 30,
140+
'yearly': 365,
141+
'forever': None,
142+
}[page], limit=limit)
143+
144+
work_index = get_solr_works(f"/works/OL{book['work_id']}W" for book in logged_books)
145+
availability_index = get_availabilities(work_index.values())
146+
for work_key in availability_index:
147+
work_index[work_key]['availability'] = availability_index[work_key]
148+
for i, logged_book in enumerate(logged_books):
149+
key = f"/works/OL{logged_book['work_id']}W"
150+
if key in work_index:
151+
logged_books[i]['work'] = work_index[key]
152+
return app.render_template("trending", logged_books=logged_books, mode=page)
153+
91154

92155
class readinglog_stats(app.view):
93156
path = "/stats/readinglog"
94157

95158
def GET(self):
96159
MAX_LEADERBOARD_SIZE = 50
97-
i = web.input(limit="10")
98-
limit = int(i.limit) if int(i.limit) < 51 else 50
160+
i = web.input(limit="10", mode="all")
161+
limit = min(int(i.limit), 50)
99162

100163
stats = get_cached_reading_log_stats(limit=limit)
101164

static/css/base/common.less

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ td {
2828
padding: 0;
2929
}
3030
h1 {
31-
margin: 20px;
31+
margin: 20px 0;
3232
}
3333
button {
3434
outline: none;

0 commit comments

Comments
 (0)