feedvault.se/feeds/views.py
2024-05-20 04:34:51 +02:00

175 lines
6.1 KiB
Python

from __future__ import annotations
import logging
from typing import TYPE_CHECKING, Any
from django.contrib import messages
from django.core.paginator import EmptyPage, Page, Paginator
from django.db.models.manager import BaseManager
from django.http import HttpRequest, HttpResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.template import loader
from django.views import View
from reader import InvalidFeedURLError
from feeds.get_reader import get_reader
from feeds.models import Entry, Feed, UploadedFeed
if TYPE_CHECKING:
from django.core.files.uploadedfile import UploadedFile
from django.db.models.manager import BaseManager
from reader import Reader
logger: logging.Logger = logging.getLogger(__name__)
class HtmxHttpRequest(HttpRequest):
htmx: Any
class IndexView(View):
"""Index path."""
def get(self, request: HttpRequest) -> HttpResponse:
"""Load the index page."""
template = loader.get_template(template_name="index.html")
context: dict[str, str] = {
"description": "FeedVault allows users to archive and search their favorite web feeds.",
"keywords": "feed, rss, atom, archive, rss list",
"author": "TheLovinator",
"canonical": "https://feedvault.se/",
"title": "FeedVault",
}
return HttpResponse(content=template.render(context=context, request=request))
class FeedView(View):
"""A single feed."""
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse: # noqa: ANN002, ANN003
"""Load the feed page."""
feed_id: str = kwargs.get("feed_id", None)
if not feed_id:
return HttpResponse(content="No id", status=400)
feed: Feed = get_object_or_404(Feed, pk=feed_id)
entries: BaseManager[Entry] = Entry.objects.filter(feed=feed).order_by("-added")[:100]
context: dict[str, Any] = {
"feed": feed,
"entries": entries,
"description": f"{feed.subtitle}" or f"Archive of {feed.url}",
"keywords": "feed, rss, atom, archive, rss list",
"author": f"{feed.author}" or "FeedVault",
"canonical": f"https://feedvault.se/feed/{feed.pk}/",
"title": f"{feed.title}" or "FeedVault",
}
return render(request=request, template_name="feed.html", context=context)
class FeedsView(View):
"""All feeds."""
def get(self, request: HtmxHttpRequest) -> HttpResponse:
"""All feeds."""
feeds: BaseManager[Feed] = Feed.objects.only("id", "url")
paginator = Paginator(object_list=feeds, per_page=100)
page_number = int(request.GET.get("page", default=1))
try:
pages: Page = paginator.get_page(page_number)
except EmptyPage:
return HttpResponse("")
context: dict[str, str | Page | int] = {
"feeds": pages,
"description": "An archive of web 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(View):
"""Add a feed."""
def get(self, request: HttpRequest) -> HttpResponse:
"""Load the index page."""
template = loader.get_template(template_name="index.html")
context: dict[str, str] = {
"description": "FeedVault allows users to archive and search their favorite web feeds.",
"keywords": "feed, rss, atom, archive, rss list",
"author": "TheLovinator",
"canonical": "https://feedvault.se/",
}
return HttpResponse(content=template.render(context=context, request=request))
def post(self, request: HttpRequest) -> HttpResponse:
"""Add a feed."""
urls: str | None = request.POST.get("urls", None)
if not urls:
return HttpResponse(content="No urls", status=400)
reader: Reader = get_reader()
for url in urls.split("\n"):
clean_url: str = url.strip()
try:
reader.add_feed(clean_url)
messages.success(request, f"Added {clean_url}")
except InvalidFeedURLError:
logger.exception("Error adding %s", clean_url)
messages.error(request, f"Error adding {clean_url}")
messages.success(request, "Feeds added")
return redirect("feeds:index")
class UploadView(View):
"""Upload a file."""
def post(self, request: HttpRequest) -> HttpResponse:
"""Upload a file."""
file: UploadedFile | None = request.FILES.get("file", None)
if not file:
return HttpResponse(content="No file", status=400)
# Save file to media folder
UploadedFeed.objects.create(user=request.user, file=file, original_filename=file.name)
# Render the index page.
messages.success(request, f"{file.name} uploaded")
messages.info(request, "If the file was marked as public, it will be shown on the feeds page. ")
return redirect("feeds:index")
class SearchView(View):
"""Search view."""
def get(self, request: HtmxHttpRequest) -> HttpResponse:
"""Load the search page."""
query: str | None = request.GET.get("q", None)
if not query:
return FeedsView().get(request)
# TODO(TheLovinator): #20 Search more fields
# https://github.com/TheLovinator1/FeedVault/issues/20
feeds: BaseManager[Feed] = Feed.objects.filter(url__icontains=query).order_by("-added")[:100]
context = {
"feeds": feeds,
"description": f"Search results for {query}",
"keywords": f"feed, rss, atom, archive, rss list, {query}",
"author": "TheLovinator",
"canonical": f"https://feedvault.se/search/?q={query}",
"title": f"Search results for {query}",
"query": query,
}
return render(request, "search.html", context)