Add infinity scrolling for /feeds

This commit is contained in:
Joakim Hellsén 2024-03-26 02:31:53 +01:00
commit d97f980b66
No known key found for this signature in database
GPG key ID: D196AE66FEBE1DC9
7 changed files with 67 additions and 33 deletions

View file

@ -65,6 +65,7 @@ INSTALLED_APPS: list[str] = [
"django.contrib.sessions", "django.contrib.sessions",
"django.contrib.messages", "django.contrib.messages",
"django.contrib.sitemaps", "django.contrib.sitemaps",
"django_htmx",
] ]
MIDDLEWARE: list[str] = [ MIDDLEWARE: list[str] = [
@ -77,6 +78,7 @@ MIDDLEWARE: list[str] = [
"django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware", "django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware",
"django_htmx.middleware.HtmxMiddleware",
] ]
# Use PostgreSQL as the default database # Use PostgreSQL as the default database

View file

@ -13,6 +13,7 @@ from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.views import LoginView, LogoutView, PasswordChangeView from django.contrib.auth.views import LoginView, LogoutView, PasswordChangeView
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.core.exceptions import SuspiciousOperation from django.core.exceptions import SuspiciousOperation
from django.core.paginator import EmptyPage, Paginator
from django.db.models.manager import BaseManager from django.db.models.manager import BaseManager
from django.http import FileResponse, Http404, HttpRequest, HttpResponse from django.http import FileResponse, Http404, HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, render from django.shortcuts import get_object_or_404, render
@ -20,7 +21,6 @@ from django.template import loader
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.views import View from django.views import View
from django.views.generic.edit import CreateView from django.views.generic.edit import CreateView
from django.views.generic.list import ListView
from feedvault.feeds import add_url from feedvault.feeds import add_url
from feedvault.models import Domain, Entry, Feed, FeedAddResult, UserUploadedFile from feedvault.models import Domain, Entry, Feed, FeedAddResult, UserUploadedFile
@ -33,6 +33,10 @@ if TYPE_CHECKING:
logger: logging.Logger = logging.getLogger(__name__) logger: logging.Logger = logging.getLogger(__name__)
class HtmxHttpRequest(HttpRequest):
htmx: Any
class IndexView(View): class IndexView(View):
"""Index path.""" """Index path."""
@ -74,24 +78,33 @@ class FeedView(View):
return render(request, "feed.html", context) return render(request, "feed.html", context)
class FeedsView(ListView): class FeedsView(View):
"""All feeds.""" """All feeds."""
model = Feed def get(self, request: HtmxHttpRequest) -> HttpResponse:
paginate_by = 100 """All feeds."""
template_name = "feeds.html" feeds: BaseManager[Feed] = Feed.objects.only("id", "feed_url")
context_object_name = "feeds"
def get_context_data(self, **kwargs) -> dict: # noqa: ANN003 paginator = Paginator(object_list=feeds, per_page=100)
"""Get the context data.""" page_number = int(request.GET.get("page", default=1))
context = super().get_context_data(**kwargs)
feed_amount: int = Feed.objects.count() or 0 try:
context["description"] = f"Archiving {feed_amount} feeds" pages = paginator.get_page(page_number)
context["keywords"] = "feed, rss, atom, archive, rss list" except EmptyPage:
context["author"] = "TheLovinator" return HttpResponse("")
context["canonical"] = "https://feedvault.se/feeds/"
context["title"] = "Feeds" context = {
return context "feeds": pages,
"description": "An archive of all feeds",
"keywords": "feed, rss, atom, archive, rss list",
"author": "TheLovinator",
"canonical": "https://feedvault.se/feeds/",
"title": "Feeds",
"page": page_number,
}
template_name = "partials/feeds.html" if request.htmx else "feeds.html"
return render(request, template_name, context)
class AddView(LoginRequiredMixin, View): class AddView(LoginRequiredMixin, View):

17
poetry.lock generated
View file

@ -463,6 +463,21 @@ files = [
django = ">=3.2.4" django = ">=3.2.4"
sqlparse = ">=0.2" sqlparse = ">=0.2"
[[package]]
name = "django-htmx"
version = "1.17.3"
description = "Extensions for using Django with htmx."
optional = false
python-versions = ">=3.8"
files = [
{file = "django-htmx-1.17.3.tar.gz", hash = "sha256:a2069219920d7ef0883ddbf5e8d931069db145a0d4a8a032a2708f840c7a68a6"},
{file = "django_htmx-1.17.3-py3-none-any.whl", hash = "sha256:0de964ca257eda2a4ebeeaa8181320119378fa5f95a2fc2f2bfbdd35034ed424"},
]
[package.dependencies]
asgiref = ">=3.6"
Django = ">=3.2"
[[package]] [[package]]
name = "django-ninja" name = "django-ninja"
version = "1.1.0" version = "1.1.0"
@ -1299,4 +1314,4 @@ brotli = ["Brotli"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.12" python-versions = "^3.12"
content-hash = "67041176bd9ca4289c6900d794465da1df4a966ed81625367552fa238b7976f8" content-hash = "e21c650c4ac009f1e6ba8939aa6236a63b8dc3a095b803fe7e13a529fc62b072"

View file

@ -7,7 +7,7 @@ readme = "README.md"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.12" python = "^3.12"
django = {extras = ["argon2"], version = "^5.0.3"} django = { extras = ["argon2"], version = "^5.0.3" }
python-dotenv = "^1.0.1" python-dotenv = "^1.0.1"
feedparser = "^6.0.11" feedparser = "^6.0.11"
gunicorn = "^21.2.0" gunicorn = "^21.2.0"
@ -15,9 +15,10 @@ dateparser = "^1.2.0"
discord-webhook = "^1.3.1" discord-webhook = "^1.3.1"
django-ninja = "^1.1.0" django-ninja = "^1.1.0"
django-debug-toolbar = "^4.3.0" django-debug-toolbar = "^4.3.0"
whitenoise = {extras = ["brotli"], version = "^6.6.0"} whitenoise = { extras = ["brotli"], version = "^6.6.0" }
rich = "^13.7.1" rich = "^13.7.1"
psycopg = {extras = ["binary"], version = "^3.1.18"} psycopg = { extras = ["binary"], version = "^3.1.18" }
django-htmx = "^1.17.3"
[tool.poetry.group.dev.dependencies] [tool.poetry.group.dev.dependencies]
ruff = "^0.3.0" ruff = "^0.3.0"
@ -25,9 +26,7 @@ djlint = "^1.34.1"
[build-system] [build-system]
build-backend = "poetry.core.masonry.api" build-backend = "poetry.core.masonry.api"
requires = [ requires = ["poetry-core"]
"poetry-core",
]
[tool.ruff] [tool.ruff]
exclude = ["migrations"] exclude = ["migrations"]

View file

@ -9,7 +9,7 @@
{% if author %}<meta name="author" content="{{ author }}" />{% endif %} {% if author %}<meta name="author" content="{{ author }}" />{% endif %}
{% if canonical %}<link rel="canonical" href="{{ canonical }}" />{% endif %} {% if canonical %}<link rel="canonical" href="{{ canonical }}" />{% endif %}
<title>{{ title|default:"FeedVault" }}</title> <title>{{ title|default:"FeedVault" }}</title>
<script src="{% static 'htmx.min.js' %}"></script> <script src="{% static 'htmx.min.js' %}" defer></script>
<style> <style>
html { html {
max-width: 88ch; max-width: 88ch;

View file

@ -1,12 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<h2>Latest Feeds</h2> <h2>Latest Feeds</h2>
{% if feeds %} {% include "partials/feeds.html" %}
{% for feed in feeds %}
<a href="{% url 'feed' feed.id %}">{{ feed.feed_url|default:"Unknown Feed" }} →</a>
<br>
{% endfor %}
{% else %}
<p>No feeds yet. Time to add some!</p>
{% endif %}
{% endblock %} {% endblock %}

View file

@ -0,0 +1,12 @@
{% if feeds %}
{% for feed in feeds %}
<a href="{% url 'feed' feed.id %}">{{ feed.feed_url|default:"Unknown Feed" }} →</a>
<br>
{% endfor %}
{% else %}
<p>No feeds yet. Time to add some!</p>
{% endif %}
<div hx-get="{% url 'feeds' %}?page={{ page|add:1 }}"
hx-trigger="revealed"
hx-target="this"
hx-swap="outerHTML">Loading...</div>