From 6e0ea8d6c871bd79e80b87de6cc88ea1cc5e6118 Mon Sep 17 00:00:00 2001
From: Arjun Satarkar
Date: Sat, 29 Jul 2023 03:56:31 +0530
Subject: Add filtering by tag and feed, improve modularity of some HTML
---
README.md | 3 +--
serve.py | 19 +++++++++++++--
tagrss.py | 62 +++++++++++++++++++++++++++++++++++++++++-------
views/add_feed.tpl | 2 +-
views/hover_help.tpl | 1 +
views/index.tpl | 35 ++++++++++++++++++++++++++-
views/list_feeds.tpl | 4 +++-
views/manage_feed.tpl | 2 +-
views/tag_hover_help.tpl | 1 +
9 files changed, 113 insertions(+), 16 deletions(-)
create mode 100644 views/hover_help.tpl
create mode 100644 views/tag_hover_help.tpl
diff --git a/README.md b/README.md
index 58300e2..2fe658a 100644
--- a/README.md
+++ b/README.md
@@ -14,10 +14,9 @@ See `LICENSE.txt` in the root of this repository for the text of the license.
## To do
-* Add filtering by tag/feed
+* Add JS to make the feed/tag input situation work like one would normally expect rather than like it's 1985. (Progressive enhancement, though.)
* Do more user input validation
* Handle more `requests` and `feedparser` error conditions
-* Add logging
* Add some reasonably high internal limit on tag count
* Add support for authentication
* Allow specifying update interval on a per-feed basis
diff --git a/serve.py b/serve.py
index f1061b1..be650a7 100755
--- a/serve.py
+++ b/serve.py
@@ -71,9 +71,20 @@ def index():
per_page: int = min(MAX_PER_PAGE_ENTRIES, int(bottle.request.query.get("per_page", DEFAULT_PER_PAGE_ENTRIES))) # type: ignore
page_num = int(bottle.request.query.get("page_num", 1)) # type: ignore
offset = (page_num - 1) * per_page
+ included_feeds_str: typing.Optional[str] = bottle.request.query.get("included_feeds", None) # type: ignore
+ included_feeds: typing.Optional[list[int]] = None
+ if included_feeds_str:
+ try:
+ included_feeds = [int(feed_id) for feed_id in included_feeds_str.split(" ")]
+ except ValueError:
+ pass
+ included_tags_str: typing.Optional[str] = bottle.request.query.get("included_tags", None) # type: ignore
+ included_tags: typing.Optional[list[str]] = None
+ if included_tags_str:
+ included_tags = parse_space_separated_tags(included_tags_str)
with core_lock:
- total_pages: int = max(1, math.ceil(core.get_entry_count() / per_page))
- entries = core.get_entries(limit=per_page, offset=offset)
+ total_pages: int = max(1, math.ceil(core.get_entry_count(included_feeds=included_feeds) / per_page))
+ entries = core.get_entries(limit=per_page, offset=offset, included_feeds=included_feeds, included_tags=included_tags)
return bottle.template(
"index",
entries=entries,
@@ -82,6 +93,10 @@ def index():
total_pages=total_pages,
per_page=per_page,
max_per_page=MAX_PER_PAGE_ENTRIES,
+ included_feeds=included_feeds,
+ included_tags=included_tags,
+ included_feeds_str=included_feeds_str,
+ included_tags_str=included_tags_str,
core=core,
)
diff --git a/tagrss.py b/tagrss.py
index 7c37bec..6ac5270 100644
--- a/tagrss.py
+++ b/tagrss.py
@@ -89,13 +89,34 @@ class TagRss:
self.store_feed_entries(feed_id, parsed_feed, epoch_downloaded)
def get_entries(
- self, *, limit: int, offset: int = 0
+ self,
+ *,
+ limit: int,
+ offset: int = 0,
+ included_feeds: typing.Optional[typing.Collection[int]] = None,
+ included_tags: typing.Optional[typing.Collection[str]] = None,
) -> list[dict[str, typing.Any]]:
+ where_clause: str = ""
+ if included_feeds or included_tags:
+ where_clause = "WHERE 1"
+ if included_feeds:
+ where_clause += f" AND feed_id IN ({','.join('?' * len(included_feeds))})"
+ if included_tags:
+ where_clause += (
+ " AND feed_id IN (SELECT feed_id FROM feed_tags WHERE tag = ?)"
+ * len(included_tags)
+ )
with self.connection:
resp = self.connection.execute(
- "SELECT id, feed_id, title, link, epoch_published, epoch_updated FROM entries \
+ f"SELECT id, feed_id, title, link, epoch_published, epoch_updated FROM entries \
+ {where_clause} \
ORDER BY id DESC LIMIT ? OFFSET ?;",
- (limit, offset),
+ (
+ *(included_feeds if included_feeds else ()),
+ *(included_tags if included_tags else ()),
+ limit,
+ offset,
+ ),
).fetchall()
entries = []
@@ -177,11 +198,36 @@ class TagRss:
)
return feeds
- def get_entry_count(self) -> int:
- with self.connection:
- return self.connection.execute("SELECT count from entry_count;").fetchone()[
- 0
- ]
+ def get_entry_count(
+ self,
+ *,
+ included_feeds: typing.Optional[typing.Collection[int]] = None,
+ included_tags: typing.Optional[typing.Collection[str]] = None,
+ ) -> int:
+ if not (included_feeds or included_tags):
+ with self.connection:
+ return self.connection.execute(
+ "SELECT count from entry_count;"
+ ).fetchone()[0]
+ else:
+ where_clause: str = "WHERE 1"
+ if included_feeds:
+ where_clause += (
+ f" AND feed_id IN ({','.join('?' * len(included_feeds))})"
+ )
+ if included_tags:
+ where_clause += (
+ " AND feed_id IN (SELECT feed_id FROM feed_tags WHERE tag = ?)"
+ * len(included_tags)
+ )
+ with self.connection:
+ return self.connection.execute(
+ f"SELECT COUNT(*) FROM entries {where_clause};",
+ (
+ *(included_feeds if included_feeds else ()),
+ *(included_tags if included_tags else ()),
+ ),
+ ).fetchone()[0]
def get_feed_count(self) -> int:
with self.connection:
diff --git a/views/add_feed.tpl b/views/add_feed.tpl
index b08ea0e..0110bac 100644
--- a/views/add_feed.tpl
+++ b/views/add_feed.tpl
@@ -21,7 +21,7 @@
- 🛈
+ % include("tag_hover_help.tpl")
diff --git a/views/hover_help.tpl b/views/hover_help.tpl
new file mode 100644
index 0000000..5d5eb97
--- /dev/null
+++ b/views/hover_help.tpl
@@ -0,0 +1 @@
+🛈
diff --git a/views/index.tpl b/views/index.tpl
index acb6fd1..52e997f 100644
--- a/views/index.tpl
+++ b/views/index.tpl
@@ -51,6 +51,33 @@
List feeds
+
+ Filter
+
+
+