diff --git a/feedvault/tests.py b/feedvault/tests.py index adc697a..d28e8a8 100644 --- a/feedvault/tests.py +++ b/feedvault/tests.py @@ -244,3 +244,50 @@ class TestStats(TestCase): response: str = get_db_size() assert isinstance(response, str), f"Expected a string, got {response}" assert "kB" in response, f"Expected 'kB' in response, got {response}" + + +class TestSearch(TestCase): + def setUp(self) -> None: + """Create a test feed.""" + self.domain: Domain = Domain.objects.create( + name="feedvault", + url="feedvault.se", + ) + self.user: User = User.objects.create_user( + username="testuser", + email="hello@feedvault.se", + password="testpassword", # noqa: S106 + ) + self.feed: Feed = Feed.objects.create( + user=self.user, + bozo=False, + feed_url="https://feedvault.se/feed.xml", + domain=self.domain, + ) + + def test_search_page(self) -> None: + """Test if the search page is accessible.""" + response: HttpResponse = self.client.get(reverse("search")) + assert response.status_code == 200, f"Expected 200, got {response.status_code}" + + def test_search_page_search(self) -> None: + """Search for a term that doesn't exist.""" + response: HttpResponse = self.client.get(reverse("search"), {"q": "test"}) + assert response.status_code == 200, f"Expected 200, got {response.status_code}" + assert ( + "No results found" in response.content.decode() + ), f"Expected 'No results found' in response, got {response.content}" + + def test_search_page_search_found(self) -> None: + """Search for a term that exists.""" + response: HttpResponse = self.client.get(reverse("search"), {"q": "feedvault"}) + assert response.status_code == 200, f"Expected 200, got {response.status_code}" + assert "feedvault" in response.content.decode(), f"Expected 'feedvault' in response, got {response.content}" + + def test_search_page_search_empty(self) -> None: + """Search for an empty term. This should redirect to the feeds page.""" + response: HttpResponse = self.client.get(reverse("search"), {"q": ""}) + assert response.status_code == 200, f"Expected 302, got {response.status_code}" + assert ( + "Latest Feeds" in response.content.decode() + ), f"Expected 'Latest Feeds' in response, got {response.content}" diff --git a/feedvault/urls.py b/feedvault/urls.py index ab7cabe..fe19537 100644 --- a/feedvault/urls.py +++ b/feedvault/urls.py @@ -36,6 +36,7 @@ urlpatterns: list = [ {"sitemaps": sitemaps}, name="django.contrib.sitemaps.views.sitemap", ), + path(route="search/", view=views.SearchView.as_view(), name="search"), path(route="domains/", view=views.DomainsView.as_view(), name="domains"), path(route="domain//", view=views.DomainView.as_view(), name="domain"), path("api/v1/", api_v1.urls), # type: ignore # noqa: PGH003 diff --git a/feedvault/views.py b/feedvault/views.py index cb55436..d720bf2 100644 --- a/feedvault/views.py +++ b/feedvault/views.py @@ -43,7 +43,7 @@ class IndexView(View): def get(self, request: HttpRequest) -> HttpResponse: """Load the index page.""" template = loader.get_template(template_name="index.html") - context = { + 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", @@ -95,7 +95,7 @@ class FeedsView(View): context: dict[str, str | Page | int] = { "feeds": pages, - "description": "An archive of all feeds", + "description": "An archive of web feeds", "keywords": "feed, rss, atom, archive, rss list", "author": "TheLovinator", "canonical": "https://feedvault.se/feeds/", @@ -450,3 +450,27 @@ class DomainView(View): } return render(request, "domain.html", context) + + +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) + + feeds: BaseManager[Feed] = Feed.objects.filter(feed_url__icontains=query).order_by("-created_at")[: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) diff --git a/templates/base.html b/templates/base.html index 86566e7..34e9e64 100644 --- a/templates/base.html +++ b/templates/base.html @@ -96,7 +96,7 @@
-
+
diff --git a/templates/search.html b/templates/search.html new file mode 100644 index 0000000..d0f87a7 --- /dev/null +++ b/templates/search.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} +{% block content %} +

+ Searched for: + "{{ query|default:"Search" }}" +

+ {% if feeds %} + {% for feed in feeds %} + {{ feed.feed_url|default:"Unknown Feed" }} → +
+ {% endfor %} + {% else %} +

No results found.

+ {% endif %} +{% endblock %}