Add /add page and cli.py for updating feeds

This commit is contained in:
Joakim Hellsén 2024-05-21 17:25:11 +02:00
commit 8cea0bd347
No known key found for this signature in database
GPG key ID: D196AE66FEBE1DC9
8 changed files with 130 additions and 57 deletions

7
.gitignore vendored
View file

@ -159,3 +159,10 @@ cython_debug/
data/
media/
staticfiles/
# https://github.com/lemon24/reader
*.sqlite.search
*.sqlite
# When running the cli.py script, the following files are created
broken_feeds.csv

View file

@ -1,9 +1,11 @@
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING
import click
from reader import Reader, UpdateError
from reader import Feed, Reader, UpdateError, UpdateResult
from reader.types import UpdatedFeed
from app.dependencies import get_reader
@ -11,19 +13,44 @@ if TYPE_CHECKING:
from reader import UpdatedFeed
def add_broken_feed_to_csv(feed: Feed | UpdateResult | None) -> None:
"""Add a broken feed to a CSV file."""
if feed is None:
click.echo("Feed is None.", err=True)
return
with Path("broken_feeds.csv").open("a", encoding="utf-8") as f:
f.write(f"{feed.url}\n")
@click.command()
def update_feeds() -> None:
"""Update all the feeds."""
click.echo("Updating feeds...")
reader: Reader = get_reader()
for feed in reader.update_feeds_iter():
click.echo("Updating feeds...")
for feed in reader.update_feeds_iter(updates_enabled=True, workers=100):
url: str = feed.url
value: UpdatedFeed | None | UpdateError = feed.value
if value is not None and isinstance(value, UpdateError):
click.echo(f"Error updating {feed.url}: {value}")
else:
click.echo(f"Updated {feed.url}.")
if isinstance(value, UpdateError):
add_broken_feed_to_csv(feed)
reader.disable_feed_updates(url)
continue
if value is None:
click.echo(f"Feed not updated: {url}")
continue
click.echo(f"Updated feed: {url}")
click.echo("Feeds updated.")
if __name__ == "__main__":
reader: Reader = get_reader()
for feed in reader.get_feeds(updates_enabled=False):
reader.enable_feed_updates(feed)
update_feeds()

View file

@ -6,9 +6,10 @@ import time
from pathlib import Path
from typing import TYPE_CHECKING
from fastapi import APIRouter, File, Request, UploadFile
from fastapi import APIRouter, File, Form, Request, UploadFile
from fastapi.responses import FileResponse
from fastapi.templating import Jinja2Templates
from reader import FeedExistsError, InvalidFeedURLError
from app.dependencies import CommonReader, CommonStats # noqa: TCH001
from app.settings import MEDIA_ROOT
@ -131,3 +132,40 @@ async def upload_page(request: Request, stats: CommonStats):
async def contact(request: Request, stats: CommonStats):
"""Contact page."""
return templates.TemplateResponse(request=request, name="contact.html", context={"stats": stats})
@static_router.post(path="/contact", summary="Contact page.", tags=["HTML"])
async def contact_form(request: Request, stats: CommonStats, message: str = Form(...)):
"""Contact page."""
# TODO(TheLovinator): Send the message to the admin. # noqa: TD003
return {
"message": message,
"stats": stats,
}
@static_router.get(path="/add", summary="Add feeds page.", tags=["HTML"])
async def add_page(request: Request, stats: CommonStats):
"""Add feeds page."""
return templates.TemplateResponse(request=request, name="add.html", context={"stats": stats})
@static_router.post(path="/add", summary="Add feeds page.", tags=["HTML"])
async def add_feed(reader: CommonReader, stats: CommonStats, feed_urls: str = Form(...)):
"""Add feeds page."""
feed_info = []
# Each line is a feed URL.
for feed_url in feed_urls.split("\n"):
try:
reader.add_feed(feed_url.strip())
feed_info.append({"url": feed_url.strip(), "status": "Added"})
except FeedExistsError as e:
feed_info.append({"url": feed_url.strip(), "status": str(e)})
except InvalidFeedURLError as e:
feed_info.append({"url": feed_url.strip(), "status": str(e)})
return {
"feed_urls": feed_urls,
"stats": stats,
"feed_info": feed_info,
}

5
app/testboi.py Normal file
View file

@ -0,0 +1,5 @@
from reader import Reader, make_reader
reader: Reader = make_reader(url="testboi.sqlite")
reader.add_feed("http://485i.com/feed/")
reader.update_feeds()

View file

@ -1,20 +0,0 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main() -> None:
"""Run administrative tasks."""
os.environ.setdefault(key="DJANGO_SETTINGS_MODULE", value="feedvault.settings")
try:
from django.core.management import execute_from_command_line # noqa: PLC0415
except ImportError as exc:
msg = "Couldn't import Django. Have you run `poetry install` or `poetry shell`?"
raise ImportError(msg) from exc
execute_from_command_line(sys.argv)
if __name__ == "__main__":
main()

49
poetry.lock generated
View file

@ -1185,45 +1185,44 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "ruff"
version = "0.3.7"
version = "0.4.4"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0e8377cccb2f07abd25e84fc5b2cbe48eeb0fea9f1719cad7caedb061d70e5ce"},
{file = "ruff-0.3.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:15a4d1cc1e64e556fa0d67bfd388fed416b7f3b26d5d1c3e7d192c897e39ba4b"},
{file = "ruff-0.3.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d28bdf3d7dc71dd46929fafeec98ba89b7c3550c3f0978e36389b5631b793663"},
{file = "ruff-0.3.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:379b67d4f49774ba679593b232dcd90d9e10f04d96e3c8ce4a28037ae473f7bb"},
{file = "ruff-0.3.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c060aea8ad5ef21cdfbbe05475ab5104ce7827b639a78dd55383a6e9895b7c51"},
{file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ebf8f615dde968272d70502c083ebf963b6781aacd3079081e03b32adfe4d58a"},
{file = "ruff-0.3.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d48098bd8f5c38897b03604f5428901b65e3c97d40b3952e38637b5404b739a2"},
{file = "ruff-0.3.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da8a4fda219bf9024692b1bc68c9cff4b80507879ada8769dc7e985755d662ea"},
{file = "ruff-0.3.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c44e0149f1d8b48c4d5c33d88c677a4aa22fd09b1683d6a7ff55b816b5d074f"},
{file = "ruff-0.3.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3050ec0af72b709a62ecc2aca941b9cd479a7bf2b36cc4562f0033d688e44fa1"},
{file = "ruff-0.3.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a29cc38e4c1ab00da18a3f6777f8b50099d73326981bb7d182e54a9a21bb4ff7"},
{file = "ruff-0.3.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b15cc59c19edca917f51b1956637db47e200b0fc5e6e1878233d3a938384b0b"},
{file = "ruff-0.3.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e491045781b1e38b72c91247cf4634f040f8d0cb3e6d3d64d38dcf43616650b4"},
{file = "ruff-0.3.7-py3-none-win32.whl", hash = "sha256:bc931de87593d64fad3a22e201e55ad76271f1d5bfc44e1a1887edd0903c7d9f"},
{file = "ruff-0.3.7-py3-none-win_amd64.whl", hash = "sha256:5ef0e501e1e39f35e03c2acb1d1238c595b8bb36cf7a170e7c1df1b73da00e74"},
{file = "ruff-0.3.7-py3-none-win_arm64.whl", hash = "sha256:789e144f6dc7019d1f92a812891c645274ed08af6037d11fc65fcbc183b7d59f"},
{file = "ruff-0.3.7.tar.gz", hash = "sha256:d5c1aebee5162c2226784800ae031f660c350e7a3402c4d1f8ea4e97e232e3ba"},
{file = "ruff-0.4.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:29d44ef5bb6a08e235c8249294fa8d431adc1426bfda99ed493119e6f9ea1bf6"},
{file = "ruff-0.4.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c4efe62b5bbb24178c950732ddd40712b878a9b96b1d02b0ff0b08a090cbd891"},
{file = "ruff-0.4.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c8e2f1e8fc12d07ab521a9005d68a969e167b589cbcaee354cb61e9d9de9c15"},
{file = "ruff-0.4.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:60ed88b636a463214905c002fa3eaab19795679ed55529f91e488db3fe8976ab"},
{file = "ruff-0.4.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b90fc5e170fc71c712cc4d9ab0e24ea505c6a9e4ebf346787a67e691dfb72e85"},
{file = "ruff-0.4.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:8e7e6ebc10ef16dcdc77fd5557ee60647512b400e4a60bdc4849468f076f6eef"},
{file = "ruff-0.4.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9ddb2c494fb79fc208cd15ffe08f32b7682519e067413dbaf5f4b01a6087bcd"},
{file = "ruff-0.4.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c51c928a14f9f0a871082603e25a1588059b7e08a920f2f9fa7157b5bf08cfe9"},
{file = "ruff-0.4.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5eb0a4bfd6400b7d07c09a7725e1a98c3b838be557fee229ac0f84d9aa49c36"},
{file = "ruff-0.4.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b1867ee9bf3acc21778dcb293db504692eda5f7a11a6e6cc40890182a9f9e595"},
{file = "ruff-0.4.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1aecced1269481ef2894cc495647392a34b0bf3e28ff53ed95a385b13aa45768"},
{file = "ruff-0.4.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9da73eb616b3241a307b837f32756dc20a0b07e2bcb694fec73699c93d04a69e"},
{file = "ruff-0.4.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:958b4ea5589706a81065e2a776237de2ecc3e763342e5cc8e02a4a4d8a5e6f95"},
{file = "ruff-0.4.4-py3-none-win32.whl", hash = "sha256:cb53473849f011bca6e754f2cdf47cafc9c4f4ff4570003a0dad0b9b6890e876"},
{file = "ruff-0.4.4-py3-none-win_amd64.whl", hash = "sha256:424e5b72597482543b684c11def82669cc6b395aa8cc69acc1858b5ef3e5daae"},
{file = "ruff-0.4.4-py3-none-win_arm64.whl", hash = "sha256:39df0537b47d3b597293edbb95baf54ff5b49589eb7ff41926d8243caa995ea6"},
{file = "ruff-0.4.4.tar.gz", hash = "sha256:f87ea42d5cdebdc6a69761a9d0bc83ae9b3b30d0ad78952005ba6568d6c022af"},
]
[[package]]
name = "setuptools"
version = "69.5.1"
version = "70.0.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false
python-versions = ">=3.8"
files = [
{file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"},
{file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"},
{file = "setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4"},
{file = "setuptools-70.0.0.tar.gz", hash = "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"},
]
[package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
[[package]]
name = "sgmllib3k"
@ -1725,4 +1724,4 @@ watchdog = ["watchdog (>=2.3)"]
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
content-hash = "1306d3bb68f23a7887a7cb506ea7095ab0d66a779b0ccfaf36ab886b552cd186"
content-hash = "069d3a9e95892d8e057e97858f342469cb3543f8bde8d893c3592583b0d36948"

View file

@ -7,23 +7,24 @@ readme = "README.md"
[tool.poetry.dependencies]
python = "^3.12"
python-dotenv = "^1.0.1"
reader = "^3.12"
fastapi = "^0.111.0"
jinja2 = "^3.1.4"
python-multipart = "^0.0.9"
humanize = "^4.9.0"
jinja2 = "^3.1.4"
python-dotenv = "^1.0.1"
python-multipart = "^0.0.9"
reader = "^3.12"
[tool.poetry.group.dev.dependencies]
ruff = "^0.3.0"
djlint = "^1.34.1"
pre-commit = "^3.7.1"
ruff = "^0.4.4"
[build-system]
build-backend = "poetry.core.masonry.api"
requires = ["poetry-core"]
[tool.ruff]
target-version = "py312"
fix = true
unsafe-fixes = true
preview = true

16
templates/add.html Normal file
View file

@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block content %}
<h2>Add feeds</h2>
<form method="post" action='{{ url_for("add_feed") }}'>
<p>
<label for="feed_urls">Feed URLs</label>
<textarea id="feed_urls"
name="feed_urls"
rows="4"
cols="50"
required
placeholder="Enter the URLs of the feeds you want to add"></textarea>
</p>
<button type="submit">Add feeds</button>
</form>
{% endblock content %}