175 lines
6.1 KiB
Python
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)
|