Repository: internetarchive/openlibrary

Issue:
Add ability for a user to filter lists by date added
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
While I'm tracking the list of books that I want to read in the future, I'd like to be able to sort my "Want to Read" list by date added. This way, I'm able to determine which books have been in my backlog the longest, so I can finally get around to reading them.

### Describe the problem that you'd like solved
<!-- A clear and concise description of what you want to happen. -->
When viewing my "want to read" list, I should be able to sort the list by date in ascending or descending order, so I can view my most recently added books or the books I added years ago and haven't gotten around to reading.

### Proposal & Constraints
<!-- What is the proposed solution / implementation? Is there a precedent of this approach succeeding elsewhere? -->
When viewing the `/people/<user>/books/want-to-read` list, add a UI control for sorting the list by date added. It's likely that this could be the start of a broader feature request for the ability to filter the list by book title, author, etc, as right now the only controls are pagination. 

### Additional context
<!-- Add any other context or screenshots about the feature request here. -->
I've attached a screenshot of this feature's implementation in Goodreads
![image](https://user-images.githubusercontent.com/1264044/102033280-e0603e00-3d6f-11eb-8a87-c7ee2a541e62.png)


### Stakeholders
<!-- @ tag stakeholders of this bug -->





Discussion:
Hello! I'm a new contributor, should I try to contribute in this issue?

@jamesachamp could you give me some guidelines please?

Howdy @Evazisouli, this could be a good one! 

# Working with Reading Log

A mini-tutorial.

## Overview

The html template for the reading log code is here:
https://github.com/internetarchive/openlibrary/blob/master/openlibrary/templates/account/books.html

This is what the rendered page looks like:
https://openlibrary.org/people/mekBot/books/want-to-read

The template is rendered by a view method here:
https://github.com/internetarchive/openlibrary/blob/master/openlibrary/plugins/upstream/account.py#L733-L760

## Data Flow

A user visits https://openlibrary.org/people/mekBot/books/want-to-read. This url pattern is matched by https://github.com/internetarchive/openlibrary/blob/master/openlibrary/plugins/upstream/account.py#L734 within the code and thus is handled by the `public_my_books` function in https://github.com/internetarchive/openlibrary/blob/master/openlibrary/plugins/upstream/account.py#L733.

The `public_my_books` in function in `plugins/upstream/account.py` calls the `Bookshelves` API model in https://github.com/internetarchive/openlibrary/blob/master/openlibrary/core/bookshelves.py#L6 which asks/queries for Reading Log data from the database.

The `Bookshelves` model returns data back to `public_my_books` where it is used to render (i.e. passed from https://github.com/internetarchive/openlibrary/blob/master/openlibrary/plugins/upstream/account.py#L755-L759 into) the html template (https://github.com/internetarchive/openlibrary/blob/master/openlibrary/templates/account/books.html).

## An example

In #5080, you can read through a complicated example of adding a feature to the Reading Log here: https://github.com/internetarchive/openlibrary/issues/5080#issue-864008133

[This same example](https://github.com/internetarchive/openlibrary/issues/5080#issue-864008133) (which presumes to add Search filtering to the Reading Log) can be adapted to add an option to sort one's Reading Log entries by date added.



Thanks @mekarpeles! I would really like to work on it!

Resolving patch:
diff --git a/openlibrary/core/bookshelves.py b/openlibrary/core/bookshelves.py
--- a/openlibrary/core/bookshelves.py
+++ b/openlibrary/core/bookshelves.py
@@ -1,3 +1,4 @@
+from typing import Literal
 from openlibrary.utils.dateutil import DATE_ONE_MONTH_AGO, DATE_ONE_WEEK_AGO
 
 from . import db
@@ -116,7 +117,9 @@ def count_total_books_logged_by_user_per_shelf(cls, username, bookshelf_ids=None
         return dict([(i['bookshelf_id'], i['count']) for i in result]) if result else {}
 
     @classmethod
-    def get_users_logged_books(cls, username, bookshelf_id=None, limit=100, page=1):
+    def get_users_logged_books(cls, username, bookshelf_id=None, limit=100, page=1,
+                               sort: Literal['created asc', 'created desc'] =
+                               'created desc'):
         """Returns a list of Reading Log database records for books which
         the user has logged. Records are described in core/schema.py
         and include:
@@ -137,12 +140,19 @@ def get_users_logged_books(cls, username, bookshelf_id=None, limit=100, page=1):
             'offset': limit * (page - 1),
             'bookshelf_id': bookshelf_id
         }
-        query = ("SELECT * from bookshelves_books WHERE "
-                 "bookshelf_id=$bookshelf_id AND username=$username "
-                 "LIMIT $limit OFFSET $offset")
+        if sort == 'created desc':
+            query = ("SELECT * from bookshelves_books WHERE "
+                     "bookshelf_id=$bookshelf_id AND username=$username "
+                     "ORDER BY created DESC "
+                     "LIMIT $limit OFFSET $offset")
+        else:
+            query = ("SELECT * from bookshelves_books WHERE "
+                     "bookshelf_id=$bookshelf_id AND username=$username "
+                     "ORDER BY created ASC "
+                     "LIMIT $limit OFFSET $offset")
         if bookshelf_id is None:
             query = ("SELECT * from bookshelves_books WHERE "
-                 "username=$username")
+                     "username=$username")
             # XXX Removing limit, offset, etc from data looks like a bug
             # unrelated / not fixing in this PR.
             data = { 'username': username }
diff --git a/openlibrary/plugins/upstream/account.py b/openlibrary/plugins/upstream/account.py
--- a/openlibrary/plugins/upstream/account.py
+++ b/openlibrary/plugins/upstream/account.py
@@ -712,28 +712,33 @@ def process_logged_books(self, logged_books):
                 if logged_books[i]['edition_id'] else '')
         return works
 
-    def get_want_to_read(self, page=1, limit=RESULTS_PER_PAGE):
+    def get_want_to_read(self, page=1, limit=RESULTS_PER_PAGE,
+                         sort='created', sort_order='desc'):
         return self.process_logged_books(Bookshelves.get_users_logged_books(
             self.user.get_username(), bookshelf_id=Bookshelves.PRESET_BOOKSHELVES['Want to Read'],
-            page=page, limit=limit))
+            page=page, limit=limit, sort=sort + ' ' + sort_order))
 
-    def get_currently_reading(self, page=1, limit=RESULTS_PER_PAGE):
+    def get_currently_reading(self, page=1, limit=RESULTS_PER_PAGE,
+                              sort='created', sort_order='desc'):
         return self.process_logged_books(Bookshelves.get_users_logged_books(
             self.user.get_username(), bookshelf_id=Bookshelves.PRESET_BOOKSHELVES['Currently Reading'],
-            page=page, limit=limit))
+            page=page, limit=limit, sort=sort + ' ' + sort_order))
 
-    def get_already_read(self, page=1, limit=RESULTS_PER_PAGE):
+    def get_already_read(self, page=1, limit=RESULTS_PER_PAGE,
+                         sort='created', sort_order='desc'):
         return self.process_logged_books(Bookshelves.get_users_logged_books(
             self.user.get_username(), bookshelf_id=Bookshelves.PRESET_BOOKSHELVES['Already Read'],
-            page=page, limit=limit))
+            page=page, limit=limit, sort=sort + ' ' + sort_order))
 
-    def get_works(self, key, page=1, limit=RESULTS_PER_PAGE):
+    def get_works(self, key, page=1, limit=RESULTS_PER_PAGE,
+                  sort='created', sort_order='desc'):
         """
         :rtype: list of openlibrary.plugins.upstream.models.Work
         """
         key = key.lower()
         if key in self.KEYS:
-            return self.KEYS[key](page=page, limit=limit)
+            return self.KEYS[key](page=page, limit=limit,
+                                  sort=sort, sort_order=sort_order)
         else: # must be a list or invalid page!
             #works = web.ctx.site.get_many([ ... ])
             raise
@@ -751,7 +756,7 @@ class public_my_books(delegate.page):
 
     def GET(self, username, key='loans'):
         """check if user's reading log is public"""
-        i = web.input(page=1)
+        i = web.input(page=1, sort='desc')
         user = web.ctx.site.get('/people/%s' % username)
         if not user:
             return render.notfound("User %s"  % username, create=False)
@@ -767,11 +772,13 @@ def GET(self, username, key='loans'):
                         'isbn_%s' % len(s['isbn']): s['isbn']
                     })[0]) for s in sponsorships)
             else:
-                books = readlog.get_works(key, page=i.page)
+                books = readlog.get_works(key, page=i.page,
+                                          sort='created', sort_order=i.sort)
             return render['account/books'](
                 books, key, sponsorship_count=len(sponsorships),
                 reading_log_counts=readlog.reading_log_counts, lists=readlog.lists,
-                user=user, logged_in_user=logged_in_user, public=is_public
+                user=user, logged_in_user=logged_in_user, public=is_public,
+                sort_order=str(i.sort)
             )
         raise web.seeother(user.key)
 


Test patch: