plain 0.79.0__tar.gz → 0.81.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of plain might be problematic. Click here for more details.

Files changed (208) hide show
  1. {plain-0.79.0 → plain-0.81.0}/PKG-INFO +1 -1
  2. {plain-0.79.0 → plain-0.81.0}/plain/CHANGELOG.md +22 -0
  3. {plain-0.79.0 → plain-0.81.0}/plain/csrf/middleware.py +2 -2
  4. {plain-0.79.0 → plain-0.81.0}/plain/views/README.md +6 -5
  5. {plain-0.79.0 → plain-0.81.0}/plain/views/errors.py +2 -1
  6. {plain-0.79.0 → plain-0.81.0}/pyproject.toml +1 -1
  7. {plain-0.79.0 → plain-0.81.0}/tests/test_csrf.py +4 -4
  8. {plain-0.79.0 → plain-0.81.0}/.gitignore +0 -0
  9. {plain-0.79.0 → plain-0.81.0}/LICENSE +0 -0
  10. {plain-0.79.0 → plain-0.81.0}/README.md +0 -0
  11. {plain-0.79.0 → plain-0.81.0}/plain/AGENTS.md +0 -0
  12. {plain-0.79.0 → plain-0.81.0}/plain/README.md +0 -0
  13. {plain-0.79.0 → plain-0.81.0}/plain/__main__.py +0 -0
  14. {plain-0.79.0 → plain-0.81.0}/plain/assets/README.md +0 -0
  15. {plain-0.79.0 → plain-0.81.0}/plain/assets/__init__.py +0 -0
  16. {plain-0.79.0 → plain-0.81.0}/plain/assets/compile.py +0 -0
  17. {plain-0.79.0 → plain-0.81.0}/plain/assets/finders.py +0 -0
  18. {plain-0.79.0 → plain-0.81.0}/plain/assets/fingerprints.py +0 -0
  19. {plain-0.79.0 → plain-0.81.0}/plain/assets/urls.py +0 -0
  20. {plain-0.79.0 → plain-0.81.0}/plain/assets/views.py +0 -0
  21. {plain-0.79.0 → plain-0.81.0}/plain/chores/README.md +0 -0
  22. {plain-0.79.0 → plain-0.81.0}/plain/chores/__init__.py +0 -0
  23. {plain-0.79.0 → plain-0.81.0}/plain/chores/core.py +0 -0
  24. {plain-0.79.0 → plain-0.81.0}/plain/chores/registry.py +0 -0
  25. {plain-0.79.0 → plain-0.81.0}/plain/cli/README.md +0 -0
  26. {plain-0.79.0 → plain-0.81.0}/plain/cli/__init__.py +0 -0
  27. {plain-0.79.0 → plain-0.81.0}/plain/cli/agent/__init__.py +0 -0
  28. {plain-0.79.0 → plain-0.81.0}/plain/cli/agent/docs.py +0 -0
  29. {plain-0.79.0 → plain-0.81.0}/plain/cli/agent/llmdocs.py +0 -0
  30. {plain-0.79.0 → plain-0.81.0}/plain/cli/agent/md.py +0 -0
  31. {plain-0.79.0 → plain-0.81.0}/plain/cli/agent/prompt.py +0 -0
  32. {plain-0.79.0 → plain-0.81.0}/plain/cli/agent/request.py +0 -0
  33. {plain-0.79.0 → plain-0.81.0}/plain/cli/build.py +0 -0
  34. {plain-0.79.0 → plain-0.81.0}/plain/cli/changelog.py +0 -0
  35. {plain-0.79.0 → plain-0.81.0}/plain/cli/chores.py +0 -0
  36. {plain-0.79.0 → plain-0.81.0}/plain/cli/core.py +0 -0
  37. {plain-0.79.0 → plain-0.81.0}/plain/cli/docs.py +0 -0
  38. {plain-0.79.0 → plain-0.81.0}/plain/cli/formatting.py +0 -0
  39. {plain-0.79.0 → plain-0.81.0}/plain/cli/install.py +0 -0
  40. {plain-0.79.0 → plain-0.81.0}/plain/cli/output.py +0 -0
  41. {plain-0.79.0 → plain-0.81.0}/plain/cli/preflight.py +0 -0
  42. {plain-0.79.0 → plain-0.81.0}/plain/cli/print.py +0 -0
  43. {plain-0.79.0 → plain-0.81.0}/plain/cli/registry.py +0 -0
  44. {plain-0.79.0 → plain-0.81.0}/plain/cli/runtime.py +0 -0
  45. {plain-0.79.0 → plain-0.81.0}/plain/cli/scaffold.py +0 -0
  46. {plain-0.79.0 → plain-0.81.0}/plain/cli/server.py +0 -0
  47. {plain-0.79.0 → plain-0.81.0}/plain/cli/settings.py +0 -0
  48. {plain-0.79.0 → plain-0.81.0}/plain/cli/shell.py +0 -0
  49. {plain-0.79.0 → plain-0.81.0}/plain/cli/startup.py +0 -0
  50. {plain-0.79.0 → plain-0.81.0}/plain/cli/upgrade.py +0 -0
  51. {plain-0.79.0 → plain-0.81.0}/plain/cli/urls.py +0 -0
  52. {plain-0.79.0 → plain-0.81.0}/plain/cli/utils.py +0 -0
  53. {plain-0.79.0 → plain-0.81.0}/plain/csrf/README.md +0 -0
  54. {plain-0.79.0 → plain-0.81.0}/plain/debug.py +0 -0
  55. {plain-0.79.0 → plain-0.81.0}/plain/exceptions.py +0 -0
  56. {plain-0.79.0 → plain-0.81.0}/plain/forms/README.md +0 -0
  57. {plain-0.79.0 → plain-0.81.0}/plain/forms/__init__.py +0 -0
  58. {plain-0.79.0 → plain-0.81.0}/plain/forms/boundfield.py +0 -0
  59. {plain-0.79.0 → plain-0.81.0}/plain/forms/exceptions.py +0 -0
  60. {plain-0.79.0 → plain-0.81.0}/plain/forms/fields.py +0 -0
  61. {plain-0.79.0 → plain-0.81.0}/plain/forms/forms.py +0 -0
  62. {plain-0.79.0 → plain-0.81.0}/plain/http/README.md +0 -0
  63. {plain-0.79.0 → plain-0.81.0}/plain/http/__init__.py +0 -0
  64. {plain-0.79.0 → plain-0.81.0}/plain/http/cookie.py +0 -0
  65. {plain-0.79.0 → plain-0.81.0}/plain/http/middleware.py +0 -0
  66. {plain-0.79.0 → plain-0.81.0}/plain/http/multipartparser.py +0 -0
  67. {plain-0.79.0 → plain-0.81.0}/plain/http/request.py +0 -0
  68. {plain-0.79.0 → plain-0.81.0}/plain/http/response.py +0 -0
  69. {plain-0.79.0 → plain-0.81.0}/plain/internal/__init__.py +0 -0
  70. {plain-0.79.0 → plain-0.81.0}/plain/internal/files/__init__.py +0 -0
  71. {plain-0.79.0 → plain-0.81.0}/plain/internal/files/base.py +0 -0
  72. {plain-0.79.0 → plain-0.81.0}/plain/internal/files/locks.py +0 -0
  73. {plain-0.79.0 → plain-0.81.0}/plain/internal/files/move.py +0 -0
  74. {plain-0.79.0 → plain-0.81.0}/plain/internal/files/temp.py +0 -0
  75. {plain-0.79.0 → plain-0.81.0}/plain/internal/files/uploadedfile.py +0 -0
  76. {plain-0.79.0 → plain-0.81.0}/plain/internal/files/uploadhandler.py +0 -0
  77. {plain-0.79.0 → plain-0.81.0}/plain/internal/files/utils.py +0 -0
  78. {plain-0.79.0 → plain-0.81.0}/plain/internal/handlers/__init__.py +0 -0
  79. {plain-0.79.0 → plain-0.81.0}/plain/internal/handlers/base.py +0 -0
  80. {plain-0.79.0 → plain-0.81.0}/plain/internal/handlers/exception.py +0 -0
  81. {plain-0.79.0 → plain-0.81.0}/plain/internal/handlers/wsgi.py +0 -0
  82. {plain-0.79.0 → plain-0.81.0}/plain/internal/middleware/__init__.py +0 -0
  83. {plain-0.79.0 → plain-0.81.0}/plain/internal/middleware/headers.py +0 -0
  84. {plain-0.79.0 → plain-0.81.0}/plain/internal/middleware/hosts.py +0 -0
  85. {plain-0.79.0 → plain-0.81.0}/plain/internal/middleware/https.py +0 -0
  86. {plain-0.79.0 → plain-0.81.0}/plain/internal/middleware/slash.py +0 -0
  87. {plain-0.79.0 → plain-0.81.0}/plain/internal/reloader.py +0 -0
  88. {plain-0.79.0 → plain-0.81.0}/plain/json.py +0 -0
  89. {plain-0.79.0 → plain-0.81.0}/plain/logs/README.md +0 -0
  90. {plain-0.79.0 → plain-0.81.0}/plain/logs/__init__.py +0 -0
  91. {plain-0.79.0 → plain-0.81.0}/plain/logs/configure.py +0 -0
  92. {plain-0.79.0 → plain-0.81.0}/plain/logs/debug.py +0 -0
  93. {plain-0.79.0 → plain-0.81.0}/plain/logs/formatters.py +0 -0
  94. {plain-0.79.0 → plain-0.81.0}/plain/logs/loggers.py +0 -0
  95. {plain-0.79.0 → plain-0.81.0}/plain/packages/README.md +0 -0
  96. {plain-0.79.0 → plain-0.81.0}/plain/packages/__init__.py +0 -0
  97. {plain-0.79.0 → plain-0.81.0}/plain/packages/config.py +0 -0
  98. {plain-0.79.0 → plain-0.81.0}/plain/packages/registry.py +0 -0
  99. {plain-0.79.0 → plain-0.81.0}/plain/paginator.py +0 -0
  100. {plain-0.79.0 → plain-0.81.0}/plain/preflight/README.md +0 -0
  101. {plain-0.79.0 → plain-0.81.0}/plain/preflight/__init__.py +0 -0
  102. {plain-0.79.0 → plain-0.81.0}/plain/preflight/checks.py +0 -0
  103. {plain-0.79.0 → plain-0.81.0}/plain/preflight/files.py +0 -0
  104. {plain-0.79.0 → plain-0.81.0}/plain/preflight/registry.py +0 -0
  105. {plain-0.79.0 → plain-0.81.0}/plain/preflight/results.py +0 -0
  106. {plain-0.79.0 → plain-0.81.0}/plain/preflight/security.py +0 -0
  107. {plain-0.79.0 → plain-0.81.0}/plain/preflight/urls.py +0 -0
  108. {plain-0.79.0 → plain-0.81.0}/plain/runtime/README.md +0 -0
  109. {plain-0.79.0 → plain-0.81.0}/plain/runtime/__init__.py +0 -0
  110. {plain-0.79.0 → plain-0.81.0}/plain/runtime/global_settings.py +0 -0
  111. {plain-0.79.0 → plain-0.81.0}/plain/runtime/user_settings.py +0 -0
  112. {plain-0.79.0 → plain-0.81.0}/plain/runtime/utils.py +0 -0
  113. {plain-0.79.0 → plain-0.81.0}/plain/server/LICENSE +0 -0
  114. {plain-0.79.0 → plain-0.81.0}/plain/server/README.md +0 -0
  115. {plain-0.79.0 → plain-0.81.0}/plain/server/__init__.py +0 -0
  116. {plain-0.79.0 → plain-0.81.0}/plain/server/app.py +0 -0
  117. {plain-0.79.0 → plain-0.81.0}/plain/server/arbiter.py +0 -0
  118. {plain-0.79.0 → plain-0.81.0}/plain/server/config.py +0 -0
  119. {plain-0.79.0 → plain-0.81.0}/plain/server/errors.py +0 -0
  120. {plain-0.79.0 → plain-0.81.0}/plain/server/glogging.py +0 -0
  121. {plain-0.79.0 → plain-0.81.0}/plain/server/http/__init__.py +0 -0
  122. {plain-0.79.0 → plain-0.81.0}/plain/server/http/body.py +0 -0
  123. {plain-0.79.0 → plain-0.81.0}/plain/server/http/errors.py +0 -0
  124. {plain-0.79.0 → plain-0.81.0}/plain/server/http/message.py +0 -0
  125. {plain-0.79.0 → plain-0.81.0}/plain/server/http/parser.py +0 -0
  126. {plain-0.79.0 → plain-0.81.0}/plain/server/http/unreader.py +0 -0
  127. {plain-0.79.0 → plain-0.81.0}/plain/server/http/wsgi.py +0 -0
  128. {plain-0.79.0 → plain-0.81.0}/plain/server/pidfile.py +0 -0
  129. {plain-0.79.0 → plain-0.81.0}/plain/server/sock.py +0 -0
  130. {plain-0.79.0 → plain-0.81.0}/plain/server/util.py +0 -0
  131. {plain-0.79.0 → plain-0.81.0}/plain/server/workers/__init__.py +0 -0
  132. {plain-0.79.0 → plain-0.81.0}/plain/server/workers/base.py +0 -0
  133. {plain-0.79.0 → plain-0.81.0}/plain/server/workers/sync.py +0 -0
  134. {plain-0.79.0 → plain-0.81.0}/plain/server/workers/thread.py +0 -0
  135. {plain-0.79.0 → plain-0.81.0}/plain/server/workers/workertmp.py +0 -0
  136. {plain-0.79.0 → plain-0.81.0}/plain/signals/README.md +0 -0
  137. {plain-0.79.0 → plain-0.81.0}/plain/signals/__init__.py +0 -0
  138. {plain-0.79.0 → plain-0.81.0}/plain/signals/dispatch/__init__.py +0 -0
  139. {plain-0.79.0 → plain-0.81.0}/plain/signals/dispatch/dispatcher.py +0 -0
  140. {plain-0.79.0 → plain-0.81.0}/plain/signals/dispatch/license.txt +0 -0
  141. {plain-0.79.0 → plain-0.81.0}/plain/signing.py +0 -0
  142. {plain-0.79.0 → plain-0.81.0}/plain/templates/AGENTS.md +0 -0
  143. {plain-0.79.0 → plain-0.81.0}/plain/templates/README.md +0 -0
  144. {plain-0.79.0 → plain-0.81.0}/plain/templates/__init__.py +0 -0
  145. {plain-0.79.0 → plain-0.81.0}/plain/templates/core.py +0 -0
  146. {plain-0.79.0 → plain-0.81.0}/plain/templates/jinja/__init__.py +0 -0
  147. {plain-0.79.0 → plain-0.81.0}/plain/templates/jinja/environments.py +0 -0
  148. {plain-0.79.0 → plain-0.81.0}/plain/templates/jinja/extensions.py +0 -0
  149. {plain-0.79.0 → plain-0.81.0}/plain/templates/jinja/filters.py +0 -0
  150. {plain-0.79.0 → plain-0.81.0}/plain/templates/jinja/globals.py +0 -0
  151. {plain-0.79.0 → plain-0.81.0}/plain/test/README.md +0 -0
  152. {plain-0.79.0 → plain-0.81.0}/plain/test/__init__.py +0 -0
  153. {plain-0.79.0 → plain-0.81.0}/plain/test/client.py +0 -0
  154. {plain-0.79.0 → plain-0.81.0}/plain/test/encoding.py +0 -0
  155. {plain-0.79.0 → plain-0.81.0}/plain/test/exceptions.py +0 -0
  156. {plain-0.79.0 → plain-0.81.0}/plain/urls/README.md +0 -0
  157. {plain-0.79.0 → plain-0.81.0}/plain/urls/__init__.py +0 -0
  158. {plain-0.79.0 → plain-0.81.0}/plain/urls/converters.py +0 -0
  159. {plain-0.79.0 → plain-0.81.0}/plain/urls/exceptions.py +0 -0
  160. {plain-0.79.0 → plain-0.81.0}/plain/urls/patterns.py +0 -0
  161. {plain-0.79.0 → plain-0.81.0}/plain/urls/resolvers.py +0 -0
  162. {plain-0.79.0 → plain-0.81.0}/plain/urls/routers.py +0 -0
  163. {plain-0.79.0 → plain-0.81.0}/plain/urls/utils.py +0 -0
  164. {plain-0.79.0 → plain-0.81.0}/plain/utils/README.md +0 -0
  165. {plain-0.79.0 → plain-0.81.0}/plain/utils/__init__.py +0 -0
  166. {plain-0.79.0 → plain-0.81.0}/plain/utils/cache.py +0 -0
  167. {plain-0.79.0 → plain-0.81.0}/plain/utils/crypto.py +0 -0
  168. {plain-0.79.0 → plain-0.81.0}/plain/utils/datastructures.py +0 -0
  169. {plain-0.79.0 → plain-0.81.0}/plain/utils/dateparse.py +0 -0
  170. {plain-0.79.0 → plain-0.81.0}/plain/utils/deconstruct.py +0 -0
  171. {plain-0.79.0 → plain-0.81.0}/plain/utils/decorators.py +0 -0
  172. {plain-0.79.0 → plain-0.81.0}/plain/utils/duration.py +0 -0
  173. {plain-0.79.0 → plain-0.81.0}/plain/utils/encoding.py +0 -0
  174. {plain-0.79.0 → plain-0.81.0}/plain/utils/functional.py +0 -0
  175. {plain-0.79.0 → plain-0.81.0}/plain/utils/hashable.py +0 -0
  176. {plain-0.79.0 → plain-0.81.0}/plain/utils/html.py +0 -0
  177. {plain-0.79.0 → plain-0.81.0}/plain/utils/http.py +0 -0
  178. {plain-0.79.0 → plain-0.81.0}/plain/utils/inspect.py +0 -0
  179. {plain-0.79.0 → plain-0.81.0}/plain/utils/ipv6.py +0 -0
  180. {plain-0.79.0 → plain-0.81.0}/plain/utils/itercompat.py +0 -0
  181. {plain-0.79.0 → plain-0.81.0}/plain/utils/module_loading.py +0 -0
  182. {plain-0.79.0 → plain-0.81.0}/plain/utils/regex_helper.py +0 -0
  183. {plain-0.79.0 → plain-0.81.0}/plain/utils/safestring.py +0 -0
  184. {plain-0.79.0 → plain-0.81.0}/plain/utils/text.py +0 -0
  185. {plain-0.79.0 → plain-0.81.0}/plain/utils/timesince.py +0 -0
  186. {plain-0.79.0 → plain-0.81.0}/plain/utils/timezone.py +0 -0
  187. {plain-0.79.0 → plain-0.81.0}/plain/utils/tree.py +0 -0
  188. {plain-0.79.0 → plain-0.81.0}/plain/validators.py +0 -0
  189. {plain-0.79.0 → plain-0.81.0}/plain/views/__init__.py +0 -0
  190. {plain-0.79.0 → plain-0.81.0}/plain/views/base.py +0 -0
  191. {plain-0.79.0 → plain-0.81.0}/plain/views/exceptions.py +0 -0
  192. {plain-0.79.0 → plain-0.81.0}/plain/views/forms.py +0 -0
  193. {plain-0.79.0 → plain-0.81.0}/plain/views/objects.py +0 -0
  194. {plain-0.79.0 → plain-0.81.0}/plain/views/redirect.py +0 -0
  195. {plain-0.79.0 → plain-0.81.0}/plain/views/templates.py +0 -0
  196. {plain-0.79.0 → plain-0.81.0}/plain/wsgi.py +0 -0
  197. {plain-0.79.0 → plain-0.81.0}/tests/.gitignore +0 -0
  198. {plain-0.79.0 → plain-0.81.0}/tests/app/.gitignore +0 -0
  199. {plain-0.79.0 → plain-0.81.0}/tests/app/settings.py +0 -0
  200. {plain-0.79.0 → plain-0.81.0}/tests/app/test/__init__.py +0 -0
  201. {plain-0.79.0 → plain-0.81.0}/tests/app/test/default_settings.py +0 -0
  202. {plain-0.79.0 → plain-0.81.0}/tests/app/urls.py +0 -0
  203. {plain-0.79.0 → plain-0.81.0}/tests/conftest.py +0 -0
  204. {plain-0.79.0 → plain-0.81.0}/tests/test_cli.py +0 -0
  205. {plain-0.79.0 → plain-0.81.0}/tests/test_http_hosts.py +0 -0
  206. {plain-0.79.0 → plain-0.81.0}/tests/test_logs.py +0 -0
  207. {plain-0.79.0 → plain-0.81.0}/tests/test_runtime.py +0 -0
  208. {plain-0.79.0 → plain-0.81.0}/tests/test_wsgi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain
3
- Version: 0.79.0
3
+ Version: 0.81.0
4
4
  Summary: A web framework for building products with Python.
5
5
  Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
6
6
  License-File: LICENSE
@@ -1,5 +1,27 @@
1
1
  # plain changelog
2
2
 
3
+ ## [0.81.0](https://github.com/dropseed/plain/releases/plain@0.81.0) (2025-10-22)
4
+
5
+ ### What's changed
6
+
7
+ - Removed support for category-specific error template fallbacks like `4xx.html` and `5xx.html` ([9513f7c4fa](https://github.com/dropseed/plain/commit/9513f7c4fa))
8
+
9
+ ### Upgrade instructions
10
+
11
+ - If you have `4xx.html` or `5xx.html` error templates, rename them to specific status code templates (e.g., `404.html`, `500.html`) or remove them if you prefer the plain HTTP response fallback
12
+
13
+ ## [0.80.0](https://github.com/dropseed/plain/releases/plain@0.80.0) (2025-10-22)
14
+
15
+ ### What's changed
16
+
17
+ - CSRF failures now raise `SuspiciousOperation` (HTTP 400) instead of `PermissionDenied` (HTTP 403) ([ad146bde3e](https://github.com/dropseed/plain/commit/ad146bde3e))
18
+ - Error templates can now use category-specific fallbacks like `4xx.html` or `5xx.html` instead of the generic `error.html` ([716cfa3cfc](https://github.com/dropseed/plain/commit/716cfa3cfc))
19
+ - Updated error template documentation with best practices for self-contained `500.html` templates ([55cea3b522](https://github.com/dropseed/plain/commit/55cea3b522))
20
+
21
+ ### Upgrade instructions
22
+
23
+ - If you have a `templates/error.html` template, instead create specific error templates for each status code you want to customize (e.g., `400.html`, `403.html`, `404.html`, `500.html`). You can also create category-specific templates like `4xx.html` or `5xx.html` for broader coverage.
24
+
3
25
  ## [0.79.0](https://github.com/dropseed/plain/releases/plain@0.79.0) (2025-10-22)
4
26
 
5
27
  ### What's changed
@@ -5,7 +5,7 @@ from collections.abc import Callable
5
5
  from typing import TYPE_CHECKING
6
6
  from urllib.parse import urlparse
7
7
 
8
- from plain.exceptions import PermissionDenied
8
+ from plain.exceptions import SuspiciousOperation
9
9
  from plain.http import HttpMiddleware
10
10
  from plain.runtime import settings
11
11
 
@@ -34,7 +34,7 @@ class CsrfViewMiddleware(HttpMiddleware):
34
34
  allowed, reason = self.should_allow_request(request)
35
35
 
36
36
  if not allowed:
37
- raise PermissionDenied(reason)
37
+ raise SuspiciousOperation(reason)
38
38
 
39
39
  return self.get_response(request)
40
40
 
@@ -251,16 +251,17 @@ class ExampleView(DetailView):
251
251
 
252
252
  ## Error views
253
253
 
254
- HTTP errors are automatically rendered using templates. Create a template named `<status_code>.html` in your templates directory to customize the error page for that status code.
255
-
256
- For example:
254
+ HTTP errors are rendered using templates. Create templates for the errors users actually see:
257
255
 
258
256
  - `templates/404.html` - Page not found
259
257
  - `templates/403.html` - Forbidden
260
258
  - `templates/500.html` - Server error
261
- - `templates/error.html` - Generic fallback for all errors
262
259
 
263
- The templates receive a context with `status_code` and `exception` variables.
260
+ Plain looks for `{status_code}.html` templates, then returns a plain HTTP response if not found. Most apps only need the three specific templates above.
261
+
262
+ Templates receive `status_code` and `exception` in context.
263
+
264
+ **Note:** `500.html` should be self-contained - avoid extending base templates or accessing database/session, since server errors can occur during middleware or template rendering. `404.html` and `403.html` can safely extend base templates since they occur during view execution after middleware runs.
264
265
 
265
266
  ## Redirect views
266
267
 
@@ -29,7 +29,8 @@ class ErrorView(TemplateView):
29
29
  return context
30
30
 
31
31
  def get_template_names(self) -> list[str]:
32
- return [f"{self.status_code}.html", "error.html"]
32
+ # Try specific status code template (e.g. "404.html")
33
+ return [f"{self.status_code}.html"]
33
34
 
34
35
  def get_request_handler(self) -> Callable[[], Any]:
35
36
  return self.get # All methods (post, patch, etc.) will use the get()
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "plain"
3
- version = "0.79.0"
3
+ version = "0.81.0"
4
4
  description = "A web framework for building products with Python."
5
5
  authors = [{name = "Dave Gaeddert", email = "dave.gaeddert@dropseed.dev"}]
6
6
  readme = "README.md"
@@ -315,10 +315,10 @@ def test_request_factory_naturally_bypasses_csrf():
315
315
 
316
316
 
317
317
  def test_middleware_integration_rejected_request():
318
- """Rejected requests should raise PermissionDenied without calling next."""
318
+ """Rejected requests should raise SuspiciousOperation without calling next."""
319
319
  from unittest.mock import Mock
320
320
 
321
- from plain.exceptions import PermissionDenied
321
+ from plain.exceptions import SuspiciousOperation
322
322
 
323
323
  rf = RequestFactory()
324
324
  mock_get_response = Mock()
@@ -326,8 +326,8 @@ def test_middleware_integration_rejected_request():
326
326
 
327
327
  request = rf.post("/test/", headers={"Origin": "https://attacker.com"})
328
328
 
329
- # Should raise PermissionDenied
330
- with pytest.raises(PermissionDenied) as exc_info:
329
+ # Should raise SuspiciousOperation
330
+ with pytest.raises(SuspiciousOperation) as exc_info:
331
331
  csrf_middleware.process_request(request)
332
332
 
333
333
  # Should not call next middleware
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes