1from __future__ import annotations
2
3from functools import cached_property
4from typing import Any
5
6from plain.assets.views import AssetView
7from plain.http import (
8 Http404,
9 Response,
10 ResponseRedirect,
11)
12from plain.runtime import settings
13from plain.views import TemplateView, View
14
15from .exceptions import PageNotFoundError, RedirectPageError
16from .pages import Page
17from .registry import pages_registry
18
19
20class PageViewMixin:
21 @cached_property
22 def page(self) -> Page:
23 url_name = self.request.resolver_match.url_name
24
25 try:
26 return pages_registry.get_page_from_name(url_name)
27 except PageNotFoundError:
28 raise Http404()
29
30
31class PageView(PageViewMixin, TemplateView):
32 template_name = "page.html"
33
34 def get(self) -> Response:
35 """Check Accept header and serve markdown if requested."""
36 if self.page.is_markdown() and settings.PAGES_SERVE_MARKDOWN:
37 preferred = self.request.get_preferred_type(
38 "text/markdown", "text/plain", "text/html"
39 )
40 if preferred in ("text/markdown", "text/plain"):
41 markdown_content = self.page._frontmatter.content
42 response = Response(
43 markdown_content, content_type="text/plain; charset=utf-8"
44 )
45 response.headers["Vary"] = "Accept"
46 return response
47
48 return super().get()
49
50 def get_template_names(self) -> list[str]:
51 """
52 Allow for more specific user templates like
53 markdown.html or html.html
54 """
55 if template_name := self.page.get_template_name():
56 return [template_name]
57
58 return super().get_template_names()
59
60 def get_template_context(self) -> dict[str, Any]:
61 context = super().get_template_context()
62 context["page"] = self.page
63 self.page.set_template_context(context) # Pass the standard context through
64 return context
65
66
67class PageRedirectView(PageViewMixin, View):
68 def get(self) -> ResponseRedirect:
69 url = self.page.vars.get("url")
70
71 if not url:
72 raise RedirectPageError("Redirect page is missing a url")
73
74 status_code = self.page.vars.get("status_code", 302)
75 return ResponseRedirect(url, status_code=status_code)
76
77
78class PageAssetView(PageViewMixin, AssetView):
79 def get_url_path(self) -> str | None:
80 return self.page.get_url_path()
81
82 def get_asset_path(self, path: str) -> str:
83 return self.page.absolute_path
84
85 def get_debug_asset_path(self, path: str) -> str:
86 return self.page.absolute_path
87
88
89class PageMarkdownView(PageViewMixin, View):
90 def get(self) -> Response:
91 """Serve the markdown content without frontmatter."""
92 markdown_content = self.page._frontmatter.content
93 response = Response(markdown_content, content_type="text/plain; charset=utf-8")
94 response.headers["Vary"] = (
95 "Accept-Encoding" # Set Vary header for proper caching
96 )
97 return response