website/website/common/views.py

156 lines
4.9 KiB
Python

from datetime import datetime
from typing import Any, Optional
from django.contrib.syndication.views import Feed
from django.http.request import HttpRequest
from django.http.response import HttpResponse
from django.templatetags.static import static
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_control, cache_page
from django.views.defaults import ERROR_404_TEMPLATE_NAME
from django.views.generic import RedirectView, TemplateView
from wagtail.models import Page
from wagtail.query import PageQuerySet
from wagtail_favicon.models import FaviconSettings
from wagtail_favicon.utils import get_rendition_url
from website.common.utils import get_site_title
from website.contrib.singleton_page.utils import SingletonPageCache
from website.home.models import HomePage
from website.search.models import SearchPage
from .models import BasePage
class Error404View(TemplateView):
template_name = ERROR_404_TEMPLATE_NAME
def render_to_response(self, context: dict, **response_kwargs: Any) -> HttpResponse:
resolver_match = self.request.resolver_match
if not resolver_match or resolver_match.url_name != "404":
response_kwargs["status"] = 404
return super().render_to_response(context, **response_kwargs)
def get_context_data(self, **kwargs: dict) -> dict:
context = super().get_context_data(**kwargs)
context["homepage"] = HomePage.objects.get()
context["search_url"] = SingletonPageCache.get_url(SearchPage, self.request)
return context
page_not_found = Error404View.as_view()
@method_decorator(cache_control(max_age=60 * 60), name="dispatch")
class RobotsView(TemplateView):
template_name = "robots.txt"
content_type = "text/plain"
def get_context_data(self, **kwargs: dict) -> dict:
context = super().get_context_data(**kwargs)
context["sitemap"] = self.request.build_absolute_uri(reverse("sitemap"))
return context
@method_decorator(cache_control(max_age=60 * 60), name="dispatch")
class KeybaseView(TemplateView):
template_name = "keybase.txt"
content_type = "text/plain"
class AllPagesFeed(Feed):
link = "/"
def __init__(self) -> None:
self.style_tag = f'<?xml-stylesheet href="{static("contrib/pretty-feed-v3.xsl")}" type="text/xsl"?>'.encode()
super().__init__()
@method_decorator(cache_page(60 * 60))
def __call__(
self, request: HttpRequest, *args: list, **kwargs: dict
) -> HttpResponse:
self.request = request
response = super().__call__(request, *args, **kwargs)
# Override Content-Type to allow styles
response.headers["content-type"] = "application/xml"
# Inject styles
opening_xml = response.content.find(b"?>") + 2
response.content = (
response.content[:opening_xml]
+ b"\n"
+ self.style_tag
+ response.content[opening_xml:]
)
return response
def title(self) -> str:
return f"All Pages Feed :: {get_site_title()}"
def items(self) -> PageQuerySet:
return (
Page.objects.live()
.public()
.exclude(depth__lte=2)
.specific()
.order_by("-last_published_at")
)
def item_title(self, item: BasePage) -> str:
return item.title
def item_link(self, item: BasePage) -> str:
return item.get_full_url(request=self.request)
def item_pubdate(self, item: BasePage) -> datetime:
return item.first_published_at
def item_updateddate(self, item: BasePage) -> datetime:
return item.last_published_at
def item_description(self, item: BasePage) -> str:
return getattr(item, "summary", None) or item.title
def item_enclosure_url(self, item: BasePage) -> Optional[str]:
if not hasattr(item, "hero_image_url"):
return ""
hero_image_url = item.hero_image_url()
if hero_image_url and hero_image_url.startswith("/"):
return self.request.build_absolute_uri(hero_image_url)
return hero_image_url
item_enclosure_mime_type = ""
item_enclosure_length = 0
class ContentPageFeed(AllPagesFeed):
def __init__(self, posts: PageQuerySet, link: str, title: str):
self.posts = posts
self.link = link
self._title = title
super().__init__()
def title(self) -> str:
return self._title
def items(self) -> PageQuerySet:
return self.posts
@method_decorator(cache_control(max_age=60 * 60), name="dispatch")
class FaviconView(RedirectView):
def get_redirect_url(self) -> str:
favicon_settings = FaviconSettings.for_request(self.request)
size = FaviconSettings.icon_sizes[0]
# Force image to PNG
return get_rendition_url(
favicon_settings.base_favicon_image, f"fill-{size}|format-png"
)