diff --git a/feeds/urls.py b/feeds/urls.py index cfb1cff..60725e9 100644 --- a/feeds/urls.py +++ b/feeds/urls.py @@ -12,4 +12,9 @@ if TYPE_CHECKING: urlpatterns: list[URLPattern | URLResolver] = [ path("", views.feed_list, name="feed-list"), path("feeds//", views.feed_detail, name="feed-detail"), + path( + "feeds//entries//", + views.entry_detail, + name="entry-detail", + ), ] diff --git a/feeds/views.py b/feeds/views.py index 38a5d93..ab79681 100644 --- a/feeds/views.py +++ b/feeds/views.py @@ -1,3 +1,5 @@ +import html +import json from typing import TYPE_CHECKING from django.http import HttpResponse @@ -47,6 +49,7 @@ def feed_detail(request: HttpRequest, feed_id: int) -> HttpResponse: "-published_at", "-fetched_at", )[:50] + html: list[str] = [ "", f"FeedVault - {feed.url}", @@ -64,7 +67,51 @@ def feed_detail(request: HttpRequest, feed_id: int) -> HttpResponse: summary: str | None = entry.data.get("summary") if entry.data else None snippet: str = title or summary or "[no title]" html.append( - f"
  • {entry.published_at or entry.fetched_at}: {snippet} (id: {entry.entry_id})
  • ", + f"
  • {entry.published_at or entry.fetched_at}: " + f'{snippet} ' + f"(id: {entry.entry_id})
  • ", ) html.extend(("", '

    Back to list

    ', "")) return HttpResponse("\n".join(html)) + + +def entry_detail(request: HttpRequest, feed_id: int, entry_id: int) -> HttpResponse: + """View to display the details of a specific entry. + + Args: + request (HttpRequest): The HTTP request object. + feed_id (int): The ID of the feed the entry belongs to. + entry_id (int): The ID of the entry to display. + + Returns: + HttpResponse: An HTML response containing the entry details. + """ + feed: Feed = get_object_or_404(Feed, id=feed_id) + entry: Entry = get_object_or_404(Entry, id=entry_id, feed=feed) + + # Render images if present in entry.data + entry_data_html: str = "" + if entry.data: + formatted_json: str = json.dumps(entry.data, indent=2, ensure_ascii=False) + escaped_json: str = html.escape(formatted_json) + note: str = '
    Note: HTML in the JSON is escaped for display and will not be rendered as HTML.
    ' + entry_data_html: str = f"{note}
    {escaped_json}
    " + else: + entry_data_html: str = "

    [No data]

    " + + html_lines: list[str] = [ + "", + f"FeedVault - Entry {entry.entry_id}", + "

    Entry Detail

    ", + f"

    Feed: {feed.url}

    ", + f"

    Entry ID: {entry.entry_id}

    ", + f"

    Published: {entry.published_at}

    ", + f"

    Fetched: {entry.fetched_at}

    ", + f"

    Content Hash: {entry.content_hash}

    ", + f"

    Error Message: {entry.error_message or '[none]'}

    ", + "

    Entry Data

    ", + entry_data_html, + f'

    Back to feed

    ', + "", + ] + return HttpResponse("\n".join(html_lines))