plain 0.33.0__tar.gz → 0.34.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.
Files changed (152) hide show
  1. {plain-0.33.0 → plain-0.34.0}/PKG-INFO +2 -2
  2. {plain-0.33.0 → plain-0.34.0}/plain/README.md +1 -1
  3. {plain-0.33.0 → plain-0.34.0}/plain/internal/handlers/exception.py +28 -25
  4. {plain-0.33.0 → plain-0.34.0}/plain/packages/config.py +8 -16
  5. {plain-0.33.0 → plain-0.34.0}/plain/packages/registry.py +4 -4
  6. {plain-0.33.0 → plain-0.34.0}/plain/utils/cache.py +27 -1
  7. {plain-0.33.0 → plain-0.34.0}/plain/views/base.py +1 -0
  8. {plain-0.33.0 → plain-0.34.0}/plain/views/errors.py +11 -3
  9. {plain-0.33.0 → plain-0.34.0}/plain/views/templates.py +4 -0
  10. {plain-0.33.0 → plain-0.34.0}/pyproject.toml +1 -1
  11. {plain-0.33.0 → plain-0.34.0}/.gitignore +0 -0
  12. {plain-0.33.0 → plain-0.34.0}/LICENSE +0 -0
  13. {plain-0.33.0 → plain-0.34.0}/README.md +0 -0
  14. {plain-0.33.0 → plain-0.34.0}/plain/__main__.py +0 -0
  15. {plain-0.33.0 → plain-0.34.0}/plain/assets/README.md +0 -0
  16. {plain-0.33.0 → plain-0.34.0}/plain/assets/__init__.py +0 -0
  17. {plain-0.33.0 → plain-0.34.0}/plain/assets/compile.py +0 -0
  18. {plain-0.33.0 → plain-0.34.0}/plain/assets/finders.py +0 -0
  19. {plain-0.33.0 → plain-0.34.0}/plain/assets/fingerprints.py +0 -0
  20. {plain-0.33.0 → plain-0.34.0}/plain/assets/urls.py +0 -0
  21. {plain-0.33.0 → plain-0.34.0}/plain/assets/views.py +0 -0
  22. {plain-0.33.0 → plain-0.34.0}/plain/cli/README.md +0 -0
  23. {plain-0.33.0 → plain-0.34.0}/plain/cli/__init__.py +0 -0
  24. {plain-0.33.0 → plain-0.34.0}/plain/cli/core.py +0 -0
  25. {plain-0.33.0 → plain-0.34.0}/plain/cli/formatting.py +0 -0
  26. {plain-0.33.0 → plain-0.34.0}/plain/cli/print.py +0 -0
  27. {plain-0.33.0 → plain-0.34.0}/plain/cli/registry.py +0 -0
  28. {plain-0.33.0 → plain-0.34.0}/plain/cli/startup.py +0 -0
  29. {plain-0.33.0 → plain-0.34.0}/plain/csrf/README.md +0 -0
  30. {plain-0.33.0 → plain-0.34.0}/plain/csrf/middleware.py +0 -0
  31. {plain-0.33.0 → plain-0.34.0}/plain/csrf/views.py +0 -0
  32. {plain-0.33.0 → plain-0.34.0}/plain/debug.py +0 -0
  33. {plain-0.33.0 → plain-0.34.0}/plain/exceptions.py +0 -0
  34. {plain-0.33.0 → plain-0.34.0}/plain/forms/README.md +0 -0
  35. {plain-0.33.0 → plain-0.34.0}/plain/forms/__init__.py +0 -0
  36. {plain-0.33.0 → plain-0.34.0}/plain/forms/boundfield.py +0 -0
  37. {plain-0.33.0 → plain-0.34.0}/plain/forms/exceptions.py +0 -0
  38. {plain-0.33.0 → plain-0.34.0}/plain/forms/fields.py +0 -0
  39. {plain-0.33.0 → plain-0.34.0}/plain/forms/forms.py +0 -0
  40. {plain-0.33.0 → plain-0.34.0}/plain/http/README.md +0 -0
  41. {plain-0.33.0 → plain-0.34.0}/plain/http/__init__.py +0 -0
  42. {plain-0.33.0 → plain-0.34.0}/plain/http/cookie.py +0 -0
  43. {plain-0.33.0 → plain-0.34.0}/plain/http/multipartparser.py +0 -0
  44. {plain-0.33.0 → plain-0.34.0}/plain/http/request.py +0 -0
  45. {plain-0.33.0 → plain-0.34.0}/plain/http/response.py +0 -0
  46. {plain-0.33.0 → plain-0.34.0}/plain/internal/__init__.py +0 -0
  47. {plain-0.33.0 → plain-0.34.0}/plain/internal/files/__init__.py +0 -0
  48. {plain-0.33.0 → plain-0.34.0}/plain/internal/files/base.py +0 -0
  49. {plain-0.33.0 → plain-0.34.0}/plain/internal/files/locks.py +0 -0
  50. {plain-0.33.0 → plain-0.34.0}/plain/internal/files/move.py +0 -0
  51. {plain-0.33.0 → plain-0.34.0}/plain/internal/files/temp.py +0 -0
  52. {plain-0.33.0 → plain-0.34.0}/plain/internal/files/uploadedfile.py +0 -0
  53. {plain-0.33.0 → plain-0.34.0}/plain/internal/files/uploadhandler.py +0 -0
  54. {plain-0.33.0 → plain-0.34.0}/plain/internal/files/utils.py +0 -0
  55. {plain-0.33.0 → plain-0.34.0}/plain/internal/handlers/__init__.py +0 -0
  56. {plain-0.33.0 → plain-0.34.0}/plain/internal/handlers/base.py +0 -0
  57. {plain-0.33.0 → plain-0.34.0}/plain/internal/handlers/wsgi.py +0 -0
  58. {plain-0.33.0 → plain-0.34.0}/plain/internal/middleware/__init__.py +0 -0
  59. {plain-0.33.0 → plain-0.34.0}/plain/internal/middleware/headers.py +0 -0
  60. {plain-0.33.0 → plain-0.34.0}/plain/internal/middleware/https.py +0 -0
  61. {plain-0.33.0 → plain-0.34.0}/plain/internal/middleware/slash.py +0 -0
  62. {plain-0.33.0 → plain-0.34.0}/plain/json.py +0 -0
  63. {plain-0.33.0 → plain-0.34.0}/plain/logs/README.md +0 -0
  64. {plain-0.33.0 → plain-0.34.0}/plain/logs/__init__.py +0 -0
  65. {plain-0.33.0 → plain-0.34.0}/plain/logs/configure.py +0 -0
  66. {plain-0.33.0 → plain-0.34.0}/plain/logs/loggers.py +0 -0
  67. {plain-0.33.0 → plain-0.34.0}/plain/logs/utils.py +0 -0
  68. {plain-0.33.0 → plain-0.34.0}/plain/packages/README.md +0 -0
  69. {plain-0.33.0 → plain-0.34.0}/plain/packages/__init__.py +0 -0
  70. {plain-0.33.0 → plain-0.34.0}/plain/paginator.py +0 -0
  71. {plain-0.33.0 → plain-0.34.0}/plain/preflight/README.md +0 -0
  72. {plain-0.33.0 → plain-0.34.0}/plain/preflight/__init__.py +0 -0
  73. {plain-0.33.0 → plain-0.34.0}/plain/preflight/files.py +0 -0
  74. {plain-0.33.0 → plain-0.34.0}/plain/preflight/messages.py +0 -0
  75. {plain-0.33.0 → plain-0.34.0}/plain/preflight/registry.py +0 -0
  76. {plain-0.33.0 → plain-0.34.0}/plain/preflight/security.py +0 -0
  77. {plain-0.33.0 → plain-0.34.0}/plain/preflight/urls.py +0 -0
  78. {plain-0.33.0 → plain-0.34.0}/plain/runtime/README.md +0 -0
  79. {plain-0.33.0 → plain-0.34.0}/plain/runtime/__init__.py +0 -0
  80. {plain-0.33.0 → plain-0.34.0}/plain/runtime/global_settings.py +0 -0
  81. {plain-0.33.0 → plain-0.34.0}/plain/runtime/user_settings.py +0 -0
  82. {plain-0.33.0 → plain-0.34.0}/plain/signals/README.md +0 -0
  83. {plain-0.33.0 → plain-0.34.0}/plain/signals/__init__.py +0 -0
  84. {plain-0.33.0 → plain-0.34.0}/plain/signals/dispatch/__init__.py +0 -0
  85. {plain-0.33.0 → plain-0.34.0}/plain/signals/dispatch/dispatcher.py +0 -0
  86. {plain-0.33.0 → plain-0.34.0}/plain/signals/dispatch/license.txt +0 -0
  87. {plain-0.33.0 → plain-0.34.0}/plain/signing.py +0 -0
  88. {plain-0.33.0 → plain-0.34.0}/plain/templates/README.md +0 -0
  89. {plain-0.33.0 → plain-0.34.0}/plain/templates/__init__.py +0 -0
  90. {plain-0.33.0 → plain-0.34.0}/plain/templates/core.py +0 -0
  91. {plain-0.33.0 → plain-0.34.0}/plain/templates/jinja/__init__.py +0 -0
  92. {plain-0.33.0 → plain-0.34.0}/plain/templates/jinja/environments.py +0 -0
  93. {plain-0.33.0 → plain-0.34.0}/plain/templates/jinja/extensions.py +0 -0
  94. {plain-0.33.0 → plain-0.34.0}/plain/templates/jinja/filters.py +0 -0
  95. {plain-0.33.0 → plain-0.34.0}/plain/templates/jinja/globals.py +0 -0
  96. {plain-0.33.0 → plain-0.34.0}/plain/test/README.md +0 -0
  97. {plain-0.33.0 → plain-0.34.0}/plain/test/__init__.py +0 -0
  98. {plain-0.33.0 → plain-0.34.0}/plain/test/client.py +0 -0
  99. {plain-0.33.0 → plain-0.34.0}/plain/test/encoding.py +0 -0
  100. {plain-0.33.0 → plain-0.34.0}/plain/test/exceptions.py +0 -0
  101. {plain-0.33.0 → plain-0.34.0}/plain/urls/README.md +0 -0
  102. {plain-0.33.0 → plain-0.34.0}/plain/urls/__init__.py +0 -0
  103. {plain-0.33.0 → plain-0.34.0}/plain/urls/converters.py +0 -0
  104. {plain-0.33.0 → plain-0.34.0}/plain/urls/exceptions.py +0 -0
  105. {plain-0.33.0 → plain-0.34.0}/plain/urls/patterns.py +0 -0
  106. {plain-0.33.0 → plain-0.34.0}/plain/urls/resolvers.py +0 -0
  107. {plain-0.33.0 → plain-0.34.0}/plain/urls/routers.py +0 -0
  108. {plain-0.33.0 → plain-0.34.0}/plain/urls/utils.py +0 -0
  109. {plain-0.33.0 → plain-0.34.0}/plain/utils/README.md +0 -0
  110. {plain-0.33.0 → plain-0.34.0}/plain/utils/__init__.py +0 -0
  111. {plain-0.33.0 → plain-0.34.0}/plain/utils/connection.py +0 -0
  112. {plain-0.33.0 → plain-0.34.0}/plain/utils/crypto.py +0 -0
  113. {plain-0.33.0 → plain-0.34.0}/plain/utils/datastructures.py +0 -0
  114. {plain-0.33.0 → plain-0.34.0}/plain/utils/dateparse.py +0 -0
  115. {plain-0.33.0 → plain-0.34.0}/plain/utils/deconstruct.py +0 -0
  116. {plain-0.33.0 → plain-0.34.0}/plain/utils/decorators.py +0 -0
  117. {plain-0.33.0 → plain-0.34.0}/plain/utils/duration.py +0 -0
  118. {plain-0.33.0 → plain-0.34.0}/plain/utils/encoding.py +0 -0
  119. {plain-0.33.0 → plain-0.34.0}/plain/utils/functional.py +0 -0
  120. {plain-0.33.0 → plain-0.34.0}/plain/utils/hashable.py +0 -0
  121. {plain-0.33.0 → plain-0.34.0}/plain/utils/html.py +0 -0
  122. {plain-0.33.0 → plain-0.34.0}/plain/utils/http.py +0 -0
  123. {plain-0.33.0 → plain-0.34.0}/plain/utils/inspect.py +0 -0
  124. {plain-0.33.0 → plain-0.34.0}/plain/utils/ipv6.py +0 -0
  125. {plain-0.33.0 → plain-0.34.0}/plain/utils/itercompat.py +0 -0
  126. {plain-0.33.0 → plain-0.34.0}/plain/utils/module_loading.py +0 -0
  127. {plain-0.33.0 → plain-0.34.0}/plain/utils/regex_helper.py +0 -0
  128. {plain-0.33.0 → plain-0.34.0}/plain/utils/safestring.py +0 -0
  129. {plain-0.33.0 → plain-0.34.0}/plain/utils/text.py +0 -0
  130. {plain-0.33.0 → plain-0.34.0}/plain/utils/timesince.py +0 -0
  131. {plain-0.33.0 → plain-0.34.0}/plain/utils/timezone.py +0 -0
  132. {plain-0.33.0 → plain-0.34.0}/plain/utils/tree.py +0 -0
  133. {plain-0.33.0 → plain-0.34.0}/plain/validators.py +0 -0
  134. {plain-0.33.0 → plain-0.34.0}/plain/views/README.md +0 -0
  135. {plain-0.33.0 → plain-0.34.0}/plain/views/__init__.py +0 -0
  136. {plain-0.33.0 → plain-0.34.0}/plain/views/csrf.py +0 -0
  137. {plain-0.33.0 → plain-0.34.0}/plain/views/exceptions.py +0 -0
  138. {plain-0.33.0 → plain-0.34.0}/plain/views/forms.py +0 -0
  139. {plain-0.33.0 → plain-0.34.0}/plain/views/objects.py +0 -0
  140. {plain-0.33.0 → plain-0.34.0}/plain/views/redirect.py +0 -0
  141. {plain-0.33.0 → plain-0.34.0}/plain/wsgi.py +0 -0
  142. {plain-0.33.0 → plain-0.34.0}/tests/.bolt/assets_collected/assets.json +0 -0
  143. {plain-0.33.0 → plain-0.34.0}/tests/.gitignore +0 -0
  144. {plain-0.33.0 → plain-0.34.0}/tests/app/.gitignore +0 -0
  145. {plain-0.33.0 → plain-0.34.0}/tests/app/settings.py +0 -0
  146. {plain-0.33.0 → plain-0.34.0}/tests/app/test/__init__.py +0 -0
  147. {plain-0.33.0 → plain-0.34.0}/tests/app/test/default_settings.py +0 -0
  148. {plain-0.33.0 → plain-0.34.0}/tests/app/urls.py +0 -0
  149. {plain-0.33.0 → plain-0.34.0}/tests/conftest.py +0 -0
  150. {plain-0.33.0 → plain-0.34.0}/tests/test_cli.py +0 -0
  151. {plain-0.33.0 → plain-0.34.0}/tests/test_runtime.py +0 -0
  152. {plain-0.33.0 → plain-0.34.0}/tests/test_wsgi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain
3
- Version: 0.33.0
3
+ Version: 0.34.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
@@ -40,7 +40,7 @@ The `plain` package includes everything you need to start handling web requests
40
40
 
41
41
  - [plain.models](/plain-models/README.md) - Define and interact with your database models.
42
42
  - [plain.cache](/plain-cache/README.md) - A database-driven general purpose cache.
43
- - [plain.mail](/plain-mail/README.md) - Send emails with SMTP or custom backends.
43
+ - [plain.email](/plain-email/README.md) - Send emails with SMTP or custom backends.
44
44
  - [plain.sessions](/plain-sessions/README.md) - User sessions and cookies.
45
45
  - [plain.worker](/plain-worker/README.md) - Backgrounb jobs stored in the database.
46
46
  - [plain.api](/plain-api/README.md) - Build APIs with Plain views.
@@ -29,7 +29,7 @@ The `plain` package includes everything you need to start handling web requests
29
29
 
30
30
  - [plain.models](/plain-models/README.md) - Define and interact with your database models.
31
31
  - [plain.cache](/plain-cache/README.md) - A database-driven general purpose cache.
32
- - [plain.mail](/plain-mail/README.md) - Send emails with SMTP or custom backends.
32
+ - [plain.email](/plain-email/README.md) - Send emails with SMTP or custom backends.
33
33
  - [plain.sessions](/plain-sessions/README.md) - User sessions and cookies.
34
34
  - [plain.worker](/plain-worker/README.md) - Backgrounb jobs stored in the database.
35
35
  - [plain.api](/plain-api/README.md) - Build APIs with Plain views.
@@ -45,10 +45,14 @@ def convert_exception_to_response(get_response):
45
45
 
46
46
  def response_for_exception(request, exc):
47
47
  if isinstance(exc, Http404):
48
- response = get_exception_response(request, 404)
48
+ response = get_exception_response(
49
+ request=request, status_code=404, exception=None
50
+ )
49
51
 
50
52
  elif isinstance(exc, PermissionDenied):
51
- response = get_exception_response(request, 403)
53
+ response = get_exception_response(
54
+ request=request, status_code=403, exception=exc
55
+ )
52
56
  log_response(
53
57
  "Forbidden (Permission denied): %s",
54
58
  request.path,
@@ -58,7 +62,9 @@ def response_for_exception(request, exc):
58
62
  )
59
63
 
60
64
  elif isinstance(exc, MultiPartParserError):
61
- response = get_exception_response(request, 400)
65
+ response = get_exception_response(
66
+ request=request, status_code=400, exception=None
67
+ )
62
68
  log_response(
63
69
  "Bad request (Unable to parse request body): %s",
64
70
  request.path,
@@ -68,7 +74,9 @@ def response_for_exception(request, exc):
68
74
  )
69
75
 
70
76
  elif isinstance(exc, BadRequest):
71
- response = get_exception_response(request, 400)
77
+ response = get_exception_response(
78
+ request=request, status_code=400, exception=exc
79
+ )
72
80
  log_response(
73
81
  "%s: %s",
74
82
  str(exc),
@@ -91,11 +99,15 @@ def response_for_exception(request, exc):
91
99
  exc_info=exc,
92
100
  extra={"status_code": 400, "request": request},
93
101
  )
94
- response = get_exception_response(request, 400)
102
+ response = get_exception_response(
103
+ request=request, status_code=400, exception=None
104
+ )
95
105
 
96
106
  else:
97
107
  signals.got_request_exception.send(sender=None, request=request)
98
- response = get_exception_response(request, 500)
108
+ response = get_exception_response(
109
+ request=request, status_code=500, exception=None
110
+ )
99
111
  log_response(
100
112
  "%s: %s",
101
113
  response.reason_phrase,
@@ -105,34 +117,25 @@ def response_for_exception(request, exc):
105
117
  exception=exc,
106
118
  )
107
119
 
108
- # Force a TemplateResponse to be rendered.
109
- if not getattr(response, "is_rendered", True) and callable(
110
- getattr(response, "render", None)
111
- ):
112
- response = response.render()
113
-
114
120
  return response
115
121
 
116
122
 
117
- def get_exception_response(request, status_code):
123
+ def get_exception_response(*, request, status_code, exception):
118
124
  try:
119
- return get_error_view(status_code)(request)
125
+ view_class = get_error_view(status_code=status_code, exception=exception)
126
+ return view_class(request)
120
127
  except Exception:
121
128
  signals.got_request_exception.send(sender=None, request=request)
122
- return handle_uncaught_exception()
123
129
 
130
+ # In development mode, re-raise the exception to get a full stack trace
131
+ if settings.DEBUG:
132
+ raise
124
133
 
125
- def handle_uncaught_exception():
126
- """
127
- Processing for any otherwise uncaught exceptions (those that will
128
- generate HTTP 500 responses).
129
- """
130
- if settings.DEBUG:
131
- raise
132
- return ResponseServerError()
134
+ # If we can't load the view, return a 500 response
135
+ return ResponseServerError()
133
136
 
134
137
 
135
- def get_error_view(status_code):
138
+ def get_error_view(*, status_code, exception):
136
139
  views_by_status = settings.HTTP_ERROR_VIEWS
137
140
  if status_code in views_by_status:
138
141
  view = views_by_status[status_code]
@@ -142,4 +145,4 @@ def get_error_view(status_code):
142
145
  return view.as_view()
143
146
 
144
147
  # Create a standard view for any other status code
145
- return ErrorView.as_view(status_code=status_code)
148
+ return ErrorView.as_view(status_code=status_code, exception=exception)
@@ -10,7 +10,9 @@ CONFIG_MODULE_NAME = "config"
10
10
  class PackageConfig:
11
11
  """Class representing a Plain application and its configuration."""
12
12
 
13
- def __init__(self, name, *, label=""):
13
+ package_label: str
14
+
15
+ def __init__(self, name):
14
16
  # Full Python path to the application e.g. 'plain.admin.admin'.
15
17
  self.name = name
16
18
 
@@ -18,28 +20,18 @@ class PackageConfig:
18
20
  # registry when it registers the PackageConfig instance.
19
21
  self.packages_registry = None
20
22
 
21
- # The following attributes could be defined at the class level in a
22
- # subclass, hence the test-and-set pattern.
23
- if label and hasattr(self, "label"):
24
- raise ImproperlyConfigured(
25
- "PackageConfig class should not define a class label attribute and an init label"
26
- )
27
-
28
- if label:
29
- # Set the label explicitly from the init
30
- self.label = label
31
- elif not hasattr(self, "label"):
23
+ if not hasattr(self, "package_label"):
32
24
  # Last component of the Python path to the application e.g. 'admin'.
33
25
  # This value must be unique across a Plain project.
34
- self.label = self.name.rpartition(".")[2]
26
+ self.package_label = self.name.rpartition(".")[2]
35
27
 
36
- if not self.label.isidentifier():
28
+ if not self.package_label.isidentifier():
37
29
  raise ImproperlyConfigured(
38
- f"The app label '{self.label}' is not a valid Python identifier."
30
+ f"The app label '{self.package_label}' is not a valid Python identifier."
39
31
  )
40
32
 
41
33
  def __repr__(self):
42
- return f"<{self.__class__.__name__}: {self.label}>"
34
+ return f"<{self.__class__.__name__}: {self.package_label}>"
43
35
 
44
36
  @cached_property
45
37
  def path(self):
@@ -146,7 +146,7 @@ class PackagesRegistry:
146
146
  message = f"No installed app with label '{package_label}'."
147
147
  for package_config in self.get_package_configs():
148
148
  if package_config.name == package_label:
149
- message += f" Did you mean '{package_config.label}'?"
149
+ message += f" Did you mean '{package_config.package_label}'?"
150
150
  break
151
151
  raise LookupError(message)
152
152
 
@@ -179,11 +179,11 @@ class PackagesRegistry:
179
179
  class Config(PackageConfig):
180
180
  pass
181
181
  """
182
- if package_config.label in self.package_configs:
182
+ if package_config.package_label in self.package_configs:
183
183
  raise ImproperlyConfigured(
184
- f"Package labels aren't unique, duplicates: {package_config.label}"
184
+ f"Package labels aren't unique, duplicates: {package_config.package_label}"
185
185
  )
186
- self.package_configs[package_config.label] = package_config
186
+ self.package_configs[package_config.package_label] = package_config
187
187
  package_config.packages = self
188
188
 
189
189
  return package_config
@@ -15,13 +15,39 @@ An example: i18n middleware would need to distinguish caches by the
15
15
  "Accept-language" header.
16
16
  """
17
17
 
18
+ import time
18
19
  from collections import defaultdict
19
20
 
20
- from plain.utils.regex_helper import _lazy_re_compile
21
+ from .http import http_date
22
+ from .regex_helper import _lazy_re_compile
21
23
 
22
24
  cc_delim_re = _lazy_re_compile(r"\s*,\s*")
23
25
 
24
26
 
27
+ def patch_response_headers(response, cache_timeout):
28
+ """
29
+ Add HTTP caching headers to the given HttpResponse: Expires and
30
+ Cache-Control.
31
+
32
+ Each header is only added if it isn't already set.
33
+ """
34
+ if cache_timeout < 0:
35
+ cache_timeout = 0 # Can't have max-age negative
36
+ if "Expires" not in response.headers:
37
+ response.headers["Expires"] = http_date(time.time() + cache_timeout)
38
+ patch_cache_control(response, max_age=cache_timeout)
39
+
40
+
41
+ def add_never_cache_headers(response):
42
+ """
43
+ Add headers to a response to indicate that a page should never be cached.
44
+ """
45
+ patch_response_headers(response, cache_timeout=-1)
46
+ patch_cache_control(
47
+ response, no_cache=True, no_store=True, must_revalidate=True, private=True
48
+ )
49
+
50
+
25
51
  def patch_cache_control(response, **kwargs):
26
52
  """
27
53
  Patch the Cache-Control header by adding all keyword arguments to it.
@@ -20,6 +20,7 @@ class View:
20
20
  url_kwargs: dict
21
21
 
22
22
  # By default, any of these are allowed if a method is defined for it.
23
+ # To disallow a defined method, remove it from this list.
23
24
  allowed_http_methods = [
24
25
  "get",
25
26
  "post",
@@ -7,11 +7,19 @@ from .templates import TemplateView
7
7
  class ErrorView(TemplateView):
8
8
  status_code: int
9
9
 
10
- def __init__(self, status_code=None) -> None:
10
+ def __init__(self, *, status_code=None, exception=None) -> None:
11
11
  # Allow creating an ErrorView with a status code
12
12
  # e.g. ErrorView.as_view(status_code=404)
13
- if status_code is not None:
14
- self.status_code = status_code
13
+ self.status_code = status_code or self.status_code
14
+
15
+ # Allow creating an ErrorView with an exception
16
+ self.exception = exception
17
+
18
+ def get_template_context(self):
19
+ context = super().get_template_context()
20
+ context["status_code"] = self.status_code
21
+ context["exception"] = self.exception
22
+ return context
15
23
 
16
24
  def get_template_names(self) -> list[str]:
17
25
  return [f"{self.status_code}.html", "error.html"]
@@ -27,6 +27,10 @@ class TemplateView(View):
27
27
 
28
28
  template_name: str | None = None
29
29
 
30
+ def __init__(self, template_name=None):
31
+ # Allow template_name to be passed in as_view()
32
+ self.template_name = template_name or self.template_name
33
+
30
34
  def get_template_context(self) -> dict:
31
35
  return {
32
36
  "request": self.request,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "plain"
3
- version = "0.33.0"
3
+ version = "0.34.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"
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