plain 0.1.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.
- plain/README.md +33 -0
- plain/__main__.py +5 -0
- plain/assets/README.md +56 -0
- plain/assets/__init__.py +6 -0
- plain/assets/finders.py +233 -0
- plain/assets/preflight.py +14 -0
- plain/assets/storage.py +916 -0
- plain/assets/utils.py +52 -0
- plain/assets/whitenoise/__init__.py +5 -0
- plain/assets/whitenoise/base.py +259 -0
- plain/assets/whitenoise/compress.py +189 -0
- plain/assets/whitenoise/media_types.py +137 -0
- plain/assets/whitenoise/middleware.py +197 -0
- plain/assets/whitenoise/responders.py +286 -0
- plain/assets/whitenoise/storage.py +178 -0
- plain/assets/whitenoise/string_utils.py +13 -0
- plain/cli/README.md +123 -0
- plain/cli/__init__.py +3 -0
- plain/cli/cli.py +439 -0
- plain/cli/formatting.py +61 -0
- plain/cli/packages.py +73 -0
- plain/cli/print.py +9 -0
- plain/cli/startup.py +33 -0
- plain/csrf/README.md +3 -0
- plain/csrf/middleware.py +466 -0
- plain/csrf/views.py +10 -0
- plain/debug.py +23 -0
- plain/exceptions.py +242 -0
- plain/forms/README.md +14 -0
- plain/forms/__init__.py +8 -0
- plain/forms/boundfield.py +58 -0
- plain/forms/exceptions.py +11 -0
- plain/forms/fields.py +1030 -0
- plain/forms/forms.py +297 -0
- plain/http/README.md +1 -0
- plain/http/__init__.py +51 -0
- plain/http/cookie.py +20 -0
- plain/http/multipartparser.py +743 -0
- plain/http/request.py +754 -0
- plain/http/response.py +719 -0
- plain/internal/__init__.py +0 -0
- plain/internal/files/README.md +3 -0
- plain/internal/files/__init__.py +3 -0
- plain/internal/files/base.py +161 -0
- plain/internal/files/locks.py +127 -0
- plain/internal/files/move.py +102 -0
- plain/internal/files/temp.py +79 -0
- plain/internal/files/uploadedfile.py +150 -0
- plain/internal/files/uploadhandler.py +254 -0
- plain/internal/files/utils.py +78 -0
- plain/internal/handlers/__init__.py +0 -0
- plain/internal/handlers/base.py +133 -0
- plain/internal/handlers/exception.py +145 -0
- plain/internal/handlers/wsgi.py +216 -0
- plain/internal/legacy/__init__.py +0 -0
- plain/internal/legacy/__main__.py +12 -0
- plain/internal/legacy/management/__init__.py +414 -0
- plain/internal/legacy/management/base.py +692 -0
- plain/internal/legacy/management/color.py +113 -0
- plain/internal/legacy/management/commands/__init__.py +0 -0
- plain/internal/legacy/management/commands/collectstatic.py +297 -0
- plain/internal/legacy/management/sql.py +67 -0
- plain/internal/legacy/management/utils.py +175 -0
- plain/json.py +40 -0
- plain/logs/README.md +24 -0
- plain/logs/__init__.py +5 -0
- plain/logs/configure.py +39 -0
- plain/logs/loggers.py +74 -0
- plain/logs/utils.py +46 -0
- plain/middleware/README.md +3 -0
- plain/middleware/__init__.py +0 -0
- plain/middleware/clickjacking.py +52 -0
- plain/middleware/common.py +87 -0
- plain/middleware/gzip.py +64 -0
- plain/middleware/security.py +64 -0
- plain/packages/README.md +41 -0
- plain/packages/__init__.py +4 -0
- plain/packages/config.py +259 -0
- plain/packages/registry.py +438 -0
- plain/paginator.py +187 -0
- plain/preflight/README.md +3 -0
- plain/preflight/__init__.py +38 -0
- plain/preflight/compatibility/__init__.py +0 -0
- plain/preflight/compatibility/django_4_0.py +20 -0
- plain/preflight/files.py +19 -0
- plain/preflight/messages.py +88 -0
- plain/preflight/registry.py +72 -0
- plain/preflight/security/__init__.py +0 -0
- plain/preflight/security/base.py +268 -0
- plain/preflight/security/csrf.py +40 -0
- plain/preflight/urls.py +117 -0
- plain/runtime/README.md +75 -0
- plain/runtime/__init__.py +61 -0
- plain/runtime/global_settings.py +199 -0
- plain/runtime/user_settings.py +353 -0
- plain/signals/README.md +14 -0
- plain/signals/__init__.py +5 -0
- plain/signals/dispatch/__init__.py +9 -0
- plain/signals/dispatch/dispatcher.py +320 -0
- plain/signals/dispatch/license.txt +35 -0
- plain/signing.py +299 -0
- plain/templates/README.md +20 -0
- plain/templates/__init__.py +6 -0
- plain/templates/core.py +24 -0
- plain/templates/jinja/README.md +227 -0
- plain/templates/jinja/__init__.py +22 -0
- plain/templates/jinja/defaults.py +119 -0
- plain/templates/jinja/extensions.py +39 -0
- plain/templates/jinja/filters.py +28 -0
- plain/templates/jinja/globals.py +19 -0
- plain/test/README.md +3 -0
- plain/test/__init__.py +16 -0
- plain/test/client.py +985 -0
- plain/test/utils.py +255 -0
- plain/urls/README.md +3 -0
- plain/urls/__init__.py +40 -0
- plain/urls/base.py +118 -0
- plain/urls/conf.py +94 -0
- plain/urls/converters.py +66 -0
- plain/urls/exceptions.py +9 -0
- plain/urls/resolvers.py +731 -0
- plain/utils/README.md +3 -0
- plain/utils/__init__.py +0 -0
- plain/utils/_os.py +52 -0
- plain/utils/cache.py +327 -0
- plain/utils/connection.py +84 -0
- plain/utils/crypto.py +76 -0
- plain/utils/datastructures.py +345 -0
- plain/utils/dateformat.py +329 -0
- plain/utils/dateparse.py +154 -0
- plain/utils/dates.py +76 -0
- plain/utils/deconstruct.py +54 -0
- plain/utils/decorators.py +90 -0
- plain/utils/deprecation.py +6 -0
- plain/utils/duration.py +44 -0
- plain/utils/email.py +12 -0
- plain/utils/encoding.py +235 -0
- plain/utils/functional.py +456 -0
- plain/utils/hashable.py +26 -0
- plain/utils/html.py +401 -0
- plain/utils/http.py +374 -0
- plain/utils/inspect.py +73 -0
- plain/utils/ipv6.py +46 -0
- plain/utils/itercompat.py +8 -0
- plain/utils/module_loading.py +69 -0
- plain/utils/regex_helper.py +353 -0
- plain/utils/safestring.py +72 -0
- plain/utils/termcolors.py +221 -0
- plain/utils/text.py +518 -0
- plain/utils/timesince.py +138 -0
- plain/utils/timezone.py +244 -0
- plain/utils/tree.py +126 -0
- plain/validators.py +603 -0
- plain/views/README.md +268 -0
- plain/views/__init__.py +18 -0
- plain/views/base.py +107 -0
- plain/views/csrf.py +24 -0
- plain/views/errors.py +25 -0
- plain/views/exceptions.py +4 -0
- plain/views/forms.py +76 -0
- plain/views/objects.py +229 -0
- plain/views/redirect.py +72 -0
- plain/views/templates.py +66 -0
- plain/wsgi.py +11 -0
- plain-0.1.0.dist-info/LICENSE +85 -0
- plain-0.1.0.dist-info/METADATA +51 -0
- plain-0.1.0.dist-info/RECORD +169 -0
- plain-0.1.0.dist-info/WHEEL +4 -0
- plain-0.1.0.dist-info/entry_points.txt +3 -0
plain/exceptions.py
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Global Plain exception and warning classes.
|
|
3
|
+
"""
|
|
4
|
+
import operator
|
|
5
|
+
|
|
6
|
+
from plain.utils.hashable import make_hashable
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FieldDoesNotExist(Exception):
|
|
10
|
+
"""The requested model field does not exist"""
|
|
11
|
+
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PackageRegistryNotReady(Exception):
|
|
16
|
+
"""The plain.packages registry is not populated yet"""
|
|
17
|
+
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ObjectDoesNotExist(Exception):
|
|
22
|
+
"""The requested object does not exist"""
|
|
23
|
+
|
|
24
|
+
silent_variable_failure = True
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class MultipleObjectsReturned(Exception):
|
|
28
|
+
"""The query returned multiple objects when only one was expected."""
|
|
29
|
+
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SuspiciousOperation(Exception):
|
|
34
|
+
"""The user did something suspicious"""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SuspiciousMultipartForm(SuspiciousOperation):
|
|
38
|
+
"""Suspect MIME request in multipart form data"""
|
|
39
|
+
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class SuspiciousFileOperation(SuspiciousOperation):
|
|
44
|
+
"""A Suspicious filesystem operation was attempted"""
|
|
45
|
+
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class DisallowedHost(SuspiciousOperation):
|
|
50
|
+
"""HTTP_HOST header contains invalid value"""
|
|
51
|
+
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class DisallowedRedirect(SuspiciousOperation):
|
|
56
|
+
"""Redirect to scheme not in allowed list"""
|
|
57
|
+
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class TooManyFieldsSent(SuspiciousOperation):
|
|
62
|
+
"""
|
|
63
|
+
The number of fields in a GET or POST request exceeded
|
|
64
|
+
settings.DATA_UPLOAD_MAX_NUMBER_FIELDS.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
pass
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class TooManyFilesSent(SuspiciousOperation):
|
|
71
|
+
"""
|
|
72
|
+
The number of fields in a GET or POST request exceeded
|
|
73
|
+
settings.DATA_UPLOAD_MAX_NUMBER_FILES.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class RequestDataTooBig(SuspiciousOperation):
|
|
80
|
+
"""
|
|
81
|
+
The size of the request (excluding any file uploads) exceeded
|
|
82
|
+
settings.DATA_UPLOAD_MAX_MEMORY_SIZE.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
pass
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class RequestAborted(Exception):
|
|
89
|
+
"""The request was closed before it was completed, or timed out."""
|
|
90
|
+
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class BadRequest(Exception):
|
|
95
|
+
"""The request is malformed and cannot be processed."""
|
|
96
|
+
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class PermissionDenied(Exception):
|
|
101
|
+
"""The user did not have permission to do that"""
|
|
102
|
+
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class ViewDoesNotExist(Exception):
|
|
107
|
+
"""The requested view does not exist"""
|
|
108
|
+
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class ImproperlyConfigured(Exception):
|
|
113
|
+
"""Plain is somehow improperly configured"""
|
|
114
|
+
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class FieldError(Exception):
|
|
119
|
+
"""Some kind of problem with a model field."""
|
|
120
|
+
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
NON_FIELD_ERRORS = "__all__"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class ValidationError(Exception):
|
|
128
|
+
"""An error while validating data."""
|
|
129
|
+
|
|
130
|
+
def __init__(self, message, code=None, params=None):
|
|
131
|
+
"""
|
|
132
|
+
The `message` argument can be a single error, a list of errors, or a
|
|
133
|
+
dictionary that maps field names to lists of errors. What we define as
|
|
134
|
+
an "error" can be either a simple string or an instance of
|
|
135
|
+
ValidationError with its message attribute set, and what we define as
|
|
136
|
+
list or dictionary can be an actual `list` or `dict` or an instance
|
|
137
|
+
of ValidationError with its `error_list` or `error_dict` attribute set.
|
|
138
|
+
"""
|
|
139
|
+
super().__init__(message, code, params)
|
|
140
|
+
|
|
141
|
+
if isinstance(message, ValidationError):
|
|
142
|
+
if hasattr(message, "error_dict"):
|
|
143
|
+
message = message.error_dict
|
|
144
|
+
elif not hasattr(message, "message"):
|
|
145
|
+
message = message.error_list
|
|
146
|
+
else:
|
|
147
|
+
message, code, params = message.message, message.code, message.params
|
|
148
|
+
|
|
149
|
+
if isinstance(message, dict):
|
|
150
|
+
self.error_dict = {}
|
|
151
|
+
for field, messages in message.items():
|
|
152
|
+
if not isinstance(messages, ValidationError):
|
|
153
|
+
messages = ValidationError(messages)
|
|
154
|
+
self.error_dict[field] = messages.error_list
|
|
155
|
+
|
|
156
|
+
elif isinstance(message, list):
|
|
157
|
+
self.error_list = []
|
|
158
|
+
for message in message:
|
|
159
|
+
# Normalize plain strings to instances of ValidationError.
|
|
160
|
+
if not isinstance(message, ValidationError):
|
|
161
|
+
message = ValidationError(message)
|
|
162
|
+
if hasattr(message, "error_dict"):
|
|
163
|
+
self.error_list.extend(sum(message.error_dict.values(), []))
|
|
164
|
+
else:
|
|
165
|
+
self.error_list.extend(message.error_list)
|
|
166
|
+
|
|
167
|
+
else:
|
|
168
|
+
self.message = message
|
|
169
|
+
self.code = code
|
|
170
|
+
self.params = params
|
|
171
|
+
self.error_list = [self]
|
|
172
|
+
|
|
173
|
+
@property
|
|
174
|
+
def message_dict(self):
|
|
175
|
+
# Trigger an AttributeError if this ValidationError
|
|
176
|
+
# doesn't have an error_dict.
|
|
177
|
+
getattr(self, "error_dict")
|
|
178
|
+
|
|
179
|
+
return dict(self)
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def messages(self):
|
|
183
|
+
if hasattr(self, "error_dict"):
|
|
184
|
+
return sum(dict(self).values(), [])
|
|
185
|
+
return list(self)
|
|
186
|
+
|
|
187
|
+
def update_error_dict(self, error_dict):
|
|
188
|
+
if hasattr(self, "error_dict"):
|
|
189
|
+
for field, error_list in self.error_dict.items():
|
|
190
|
+
error_dict.setdefault(field, []).extend(error_list)
|
|
191
|
+
else:
|
|
192
|
+
error_dict.setdefault(NON_FIELD_ERRORS, []).extend(self.error_list)
|
|
193
|
+
return error_dict
|
|
194
|
+
|
|
195
|
+
def __iter__(self):
|
|
196
|
+
if hasattr(self, "error_dict"):
|
|
197
|
+
for field, errors in self.error_dict.items():
|
|
198
|
+
yield field, list(ValidationError(errors))
|
|
199
|
+
else:
|
|
200
|
+
for error in self.error_list:
|
|
201
|
+
message = error.message
|
|
202
|
+
if error.params:
|
|
203
|
+
message %= error.params
|
|
204
|
+
yield str(message)
|
|
205
|
+
|
|
206
|
+
def __str__(self):
|
|
207
|
+
if hasattr(self, "error_dict"):
|
|
208
|
+
return repr(dict(self))
|
|
209
|
+
return repr(list(self))
|
|
210
|
+
|
|
211
|
+
def __repr__(self):
|
|
212
|
+
return "ValidationError(%s)" % self
|
|
213
|
+
|
|
214
|
+
def __eq__(self, other):
|
|
215
|
+
if not isinstance(other, ValidationError):
|
|
216
|
+
return NotImplemented
|
|
217
|
+
return hash(self) == hash(other)
|
|
218
|
+
|
|
219
|
+
def __hash__(self):
|
|
220
|
+
if hasattr(self, "message"):
|
|
221
|
+
return hash(
|
|
222
|
+
(
|
|
223
|
+
self.message,
|
|
224
|
+
self.code,
|
|
225
|
+
make_hashable(self.params),
|
|
226
|
+
)
|
|
227
|
+
)
|
|
228
|
+
if hasattr(self, "error_dict"):
|
|
229
|
+
return hash(make_hashable(self.error_dict))
|
|
230
|
+
return hash(tuple(sorted(self.error_list, key=operator.attrgetter("message"))))
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class EmptyResultSet(Exception):
|
|
234
|
+
"""A database query predicate is impossible."""
|
|
235
|
+
|
|
236
|
+
pass
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class FullResultSet(Exception):
|
|
240
|
+
"""A database query predicate is matches everything."""
|
|
241
|
+
|
|
242
|
+
pass
|
plain/forms/README.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Forms
|
|
2
|
+
|
|
3
|
+
Handle user input.
|
|
4
|
+
|
|
5
|
+
- forms don't render themselves
|
|
6
|
+
- registered scripts vs media?
|
|
7
|
+
- move model save logic into form.save() - disconnect them
|
|
8
|
+
- Form: validates input from request, saves it
|
|
9
|
+
- Form field: validates field, knows how to parse from html data ?
|
|
10
|
+
- starter includes form components - no markup in python
|
|
11
|
+
|
|
12
|
+
## Re-using input styles
|
|
13
|
+
|
|
14
|
+
plain-elements, or includes
|
plain/forms/__init__.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from functools import cached_property
|
|
2
|
+
|
|
3
|
+
__all__ = ("BoundField",)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BoundField:
|
|
7
|
+
"A Field plus data"
|
|
8
|
+
|
|
9
|
+
def __init__(self, form, field, name):
|
|
10
|
+
self._form = form
|
|
11
|
+
self.field = field
|
|
12
|
+
self.name = name
|
|
13
|
+
self.html_name = form.add_prefix(name)
|
|
14
|
+
self.html_id = form.add_prefix(self._auto_id)
|
|
15
|
+
|
|
16
|
+
def __repr__(self):
|
|
17
|
+
return f'<{self.__class__.__name__} "{self.html_name}">'
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def errors(self):
|
|
21
|
+
"""
|
|
22
|
+
Return an error list (empty if there are no errors) for this field.
|
|
23
|
+
"""
|
|
24
|
+
return self._form.errors.get(self.name, [])
|
|
25
|
+
|
|
26
|
+
def value(self):
|
|
27
|
+
"""
|
|
28
|
+
Return the value for this BoundField, using the initial value if
|
|
29
|
+
the form is not bound or the data otherwise.
|
|
30
|
+
"""
|
|
31
|
+
data = self.initial
|
|
32
|
+
if self._form.is_bound:
|
|
33
|
+
data = self.field.bound_data(
|
|
34
|
+
self._form._field_data_value(self.field, self.html_name), data
|
|
35
|
+
)
|
|
36
|
+
return self.field.prepare_value(data)
|
|
37
|
+
|
|
38
|
+
@cached_property
|
|
39
|
+
def initial(self):
|
|
40
|
+
return self._form.get_initial_for_field(self.field, self.name)
|
|
41
|
+
|
|
42
|
+
def _has_changed(self):
|
|
43
|
+
return self.field.has_changed(
|
|
44
|
+
self.initial, self._form._field_data_value(self.field, self.html_name)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def _auto_id(self):
|
|
49
|
+
"""
|
|
50
|
+
Calculate and return the ID attribute for this BoundField, if the
|
|
51
|
+
associated Form has specified auto_id. Return an empty string otherwise.
|
|
52
|
+
"""
|
|
53
|
+
auto_id = self._form._auto_id # Boolean or string
|
|
54
|
+
if auto_id and "%s" in str(auto_id):
|
|
55
|
+
return auto_id % self.html_name
|
|
56
|
+
elif auto_id:
|
|
57
|
+
return self.html_name
|
|
58
|
+
return ""
|