plain 0.79.0__py3-none-any.whl → 0.80.0__py3-none-any.whl

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.

plain/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # plain changelog
2
2
 
3
+ ## [0.80.0](https://github.com/dropseed/plain/releases/plain@0.80.0) (2025-10-22)
4
+
5
+ ### What's changed
6
+
7
+ - CSRF failures now raise `SuspiciousOperation` (HTTP 400) instead of `PermissionDenied` (HTTP 403) ([ad146bde3e](https://github.com/dropseed/plain/commit/ad146bde3e))
8
+ - 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))
9
+ - Updated error template documentation with best practices for self-contained `500.html` templates ([55cea3b522](https://github.com/dropseed/plain/commit/55cea3b522))
10
+
11
+ ### Upgrade instructions
12
+
13
+ - 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.
14
+
3
15
  ## [0.79.0](https://github.com/dropseed/plain/releases/plain@0.79.0) (2025-10-22)
4
16
 
5
17
  ### What's changed
plain/csrf/middleware.py 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
 
plain/views/README.md CHANGED
@@ -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`, then `{category}.html` (e.g., `4xx.html`), then returns a plain HTTP response. 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
 
plain/views/errors.py CHANGED
@@ -29,7 +29,10 @@ 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 first (e.g. "404.html")
33
+ # Then fall back to category (e.g. "4xx.html" or "5xx.html")
34
+ category = f"{str(self.status_code)[0]}xx"
35
+ return [f"{self.status_code}.html", f"{category}.html"]
33
36
 
34
37
  def get_request_handler(self) -> Callable[[], Any]:
35
38
  return self.get # All methods (post, patch, etc.) will use the get()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain
3
- Version: 0.79.0
3
+ Version: 0.80.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,5 @@
1
1
  plain/AGENTS.md,sha256=As6EFSWWHJ9lYIxb2LMRqNRteH45SRs7a_VFslzF53M,1046
2
- plain/CHANGELOG.md,sha256=1maDvdJj2PRr6h9sZ-aSsiA8NL1k_m3T-nKTWBFxIIg,30779
2
+ plain/CHANGELOG.md,sha256=CZJXaqSX2G0nv_Bp-_VKzIuVGgDlwtFdD_cEek6ajpg,31725
3
3
  plain/README.md,sha256=VvzhXNvf4I6ddmjBP9AExxxFXxs7RwyoxdgFm-W5dsg,4072
4
4
  plain/__main__.py,sha256=GK39854Lc_LO_JP8DzY9Y2MIQ4cQEl7SXFJy244-lC8,110
5
5
  plain/debug.py,sha256=C2OnFHtRGMrpCiHSt-P2r58JypgQZ62qzDBpV4mhtFM,855
@@ -49,7 +49,7 @@ plain/cli/agent/md.py,sha256=eRspArtBVQNqxIii6J7sPPZqBjc-qmAF49aLnwZoWAE,2778
49
49
  plain/cli/agent/prompt.py,sha256=hMb4RXiMF68xSSHC3gXrCUjv5jZa7flQzL4FZvUMu1I,1261
50
50
  plain/cli/agent/request.py,sha256=cBdN9w4msPqfaE13DHrvAE0lkSdkTdTkIjZzBht8Ydc,6432
51
51
  plain/csrf/README.md,sha256=ApWpB-qlEf0LkOKm9Yr-6f_lB9XJEvGFDo_fraw8ghI,2391
52
- plain/csrf/middleware.py,sha256=VxnL4vHehzcu2SfrMp7v0XKUOdJEuU1oLdZRTxEO4wQ,4877
52
+ plain/csrf/middleware.py,sha256=WDSsQOjkOxgk8JU-cNiPlcK5IRXemOfmDMXPH7Riv0U,4883
53
53
  plain/forms/README.md,sha256=7MJQxNBoKkg0rW16qF6bGpUBxZrMrWjl2DZZk6gjzAU,2258
54
54
  plain/forms/__init__.py,sha256=sK-Y1QC_7iueo1BSbYrGT0uq2LooB0U8_j5VksNyZ4c,236
55
55
  plain/forms/boundfield.py,sha256=PEquPRn1BoVd4ZkIin8tjkBzRDMv-41wO8qHV0S1shg,1967
@@ -179,17 +179,17 @@ plain/utils/text.py,sha256=teav7elbqEtGnhKG3ajf-V9Hb-Gsg8uqDrogqWizqjI,10094
179
179
  plain/utils/timesince.py,sha256=a_-ZoPK_s3Pt998CW4rWp0clZ1XyK2x04hCqak2giII,5928
180
180
  plain/utils/timezone.py,sha256=M_I5yvs9NsHbtNBPJgHErvWw9vatzx4M96tRQs5gS3g,6823
181
181
  plain/utils/tree.py,sha256=rj_JpZ2kVD3UExWoKnsRdVCoRjvzkuVOONcHzREjSyw,4766
182
- plain/views/README.md,sha256=-WWjDCvheG9cmocrmkzHtk6PXhbFAKpx2vVF6-z4mV8,7137
182
+ plain/views/README.md,sha256=Kuw6tMfFp0-fu2aDFM0iCKV-FzgZmkPwd78cYKDXaUA,7438
183
183
  plain/views/__init__.py,sha256=a-N1nkklVohJTtz0yD1MMaS0g66HviEjsKydNVVjvVc,392
184
184
  plain/views/base.py,sha256=yWh6S68PsYcH1dvRdibQIanBYkjo2iJ8IAbR2PTWQrk,4419
185
- plain/views/errors.py,sha256=tHD7MNnZcMyiQ46RMAnX1Ne3Zbbkr1zAiVfJyaaLtSQ,1447
185
+ plain/views/errors.py,sha256=og20lx5WGDiQ-thOAYtWNsfjuZiLLVydpk94k810j6A,1632
186
186
  plain/views/exceptions.py,sha256=-YKH1Jd9Zm_yXiz797PVjJB6VWaPCTXClHIUkG2fq78,198
187
187
  plain/views/forms.py,sha256=ESZOXuo6IeYixp1RZvPb94KplkowRiwO2eGJCM6zJI0,2400
188
188
  plain/views/objects.py,sha256=5y0PoPPo07dQTTcJ_9kZcx0iI1O7regsooAIK4VqXQ0,5579
189
189
  plain/views/redirect.py,sha256=mIpSAFcaEyeLDyv4Fr6g_ektduG4Wfa6s6L-rkdazmM,2154
190
190
  plain/views/templates.py,sha256=9LgDMCv4C7JzLiyw8jHF-i4350ukwgixC_9y4faEGu0,1885
191
- plain-0.79.0.dist-info/METADATA,sha256=-lUH8_JrMqylAVv-K4JprhZJz4F2Kt8v1qn5Ri0xvBs,4516
192
- plain-0.79.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
193
- plain-0.79.0.dist-info/entry_points.txt,sha256=1Ys2lsSeMepD1vz8RSrJopna0RQfUd951vYvCRsvl6A,45
194
- plain-0.79.0.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
195
- plain-0.79.0.dist-info/RECORD,,
191
+ plain-0.80.0.dist-info/METADATA,sha256=jweqjIZtRoFGSJ0YCjb5dF9bcOAsPZaGbrdTCB4ktY4,4516
192
+ plain-0.80.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
193
+ plain-0.80.0.dist-info/entry_points.txt,sha256=1Ys2lsSeMepD1vz8RSrJopna0RQfUd951vYvCRsvl6A,45
194
+ plain-0.80.0.dist-info/licenses/LICENSE,sha256=m0D5O7QoH9l5Vz_rrX_9r-C8d9UNr_ciK6Qwac7o6yo,3175
195
+ plain-0.80.0.dist-info/RECORD,,
File without changes