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/views/objects.py
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
from plain.exceptions import ImproperlyConfigured, ObjectDoesNotExist
|
|
2
|
+
from plain.http import Http404, Response, ResponseRedirect
|
|
3
|
+
|
|
4
|
+
from .forms import FormView
|
|
5
|
+
from .templates import TemplateView
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ObjectTemplateViewMixin:
|
|
9
|
+
context_object_name = ""
|
|
10
|
+
|
|
11
|
+
def get(self) -> Response:
|
|
12
|
+
self.load_object()
|
|
13
|
+
return self.render_template()
|
|
14
|
+
|
|
15
|
+
def load_object(self) -> None:
|
|
16
|
+
try:
|
|
17
|
+
self.object = self.get_object()
|
|
18
|
+
except ObjectDoesNotExist:
|
|
19
|
+
raise Http404
|
|
20
|
+
|
|
21
|
+
if not self.object:
|
|
22
|
+
# Also raise 404 if the object is None
|
|
23
|
+
raise Http404
|
|
24
|
+
|
|
25
|
+
def get_object(self): # Intentionally untyped... subclasses must override this.
|
|
26
|
+
raise NotImplementedError(
|
|
27
|
+
f"get_object() is not implemented on {self.__class__.__name__}"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def get_template_context(self) -> dict:
|
|
31
|
+
"""Insert the single object into the context dict."""
|
|
32
|
+
context = super().get_template_context() # type: ignore
|
|
33
|
+
context["object"] = self.object
|
|
34
|
+
if self.context_object_name:
|
|
35
|
+
context[self.context_object_name] = self.object
|
|
36
|
+
elif hasattr(self.object, "_meta"):
|
|
37
|
+
context[self.object._meta.model_name] = self.object
|
|
38
|
+
return context
|
|
39
|
+
|
|
40
|
+
def get_template_names(self) -> list[str]:
|
|
41
|
+
"""
|
|
42
|
+
Return a list of template names to be used for the request. May not be
|
|
43
|
+
called if render_to_response() is overridden. Return the following list:
|
|
44
|
+
|
|
45
|
+
* the value of ``template_name`` on the view (if provided)
|
|
46
|
+
object instance that the view is operating upon (if available)
|
|
47
|
+
* ``<package_label>/<model_name><template_name_suffix>.html``
|
|
48
|
+
"""
|
|
49
|
+
if self.template_name: # type: ignore
|
|
50
|
+
return [self.template_name] # type: ignore
|
|
51
|
+
|
|
52
|
+
# If template_name isn't specified, it's not a problem --
|
|
53
|
+
# we just start with an empty list.
|
|
54
|
+
names = []
|
|
55
|
+
|
|
56
|
+
# The least-specific option is the default <app>/<model>_detail.html;
|
|
57
|
+
# only use this if the object in question is a model.
|
|
58
|
+
if hasattr(self.object, "_meta"):
|
|
59
|
+
object_meta = self.object._meta
|
|
60
|
+
names.append(
|
|
61
|
+
f"{object_meta.package_label}/{object_meta.model_name}{self.template_name_suffix}.html"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return names
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class DetailView(ObjectTemplateViewMixin, TemplateView):
|
|
68
|
+
"""
|
|
69
|
+
Render a "detail" view of an object.
|
|
70
|
+
|
|
71
|
+
By default this is a model instance looked up from `self.queryset`, but the
|
|
72
|
+
view will support display of *any* object by overriding `self.get_object()`.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
template_name_suffix = "_detail"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class CreateView(ObjectTemplateViewMixin, FormView):
|
|
79
|
+
"""
|
|
80
|
+
View for creating a new object, with a response rendered by a template.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
def post(self) -> Response:
|
|
84
|
+
"""
|
|
85
|
+
Handle POST requests: instantiate a form instance with the passed
|
|
86
|
+
POST variables and then check if it's valid.
|
|
87
|
+
"""
|
|
88
|
+
# Context expects self.object to exist
|
|
89
|
+
self.load_object()
|
|
90
|
+
return super().post()
|
|
91
|
+
|
|
92
|
+
def load_object(self) -> None:
|
|
93
|
+
self.object = None
|
|
94
|
+
|
|
95
|
+
# TODO? would rather you have to specify this...
|
|
96
|
+
def get_success_url(self):
|
|
97
|
+
"""Return the URL to redirect to after processing a valid form."""
|
|
98
|
+
if self.success_url:
|
|
99
|
+
url = self.success_url.format(**self.object.__dict__)
|
|
100
|
+
else:
|
|
101
|
+
try:
|
|
102
|
+
url = self.object.get_absolute_url()
|
|
103
|
+
except AttributeError:
|
|
104
|
+
raise ImproperlyConfigured(
|
|
105
|
+
"No URL to redirect to. Either provide a url or define"
|
|
106
|
+
" a get_absolute_url method on the Model."
|
|
107
|
+
)
|
|
108
|
+
return url
|
|
109
|
+
|
|
110
|
+
def form_valid(self, form):
|
|
111
|
+
"""If the form is valid, save the associated model."""
|
|
112
|
+
self.object = form.save()
|
|
113
|
+
return super().form_valid(form)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class UpdateView(ObjectTemplateViewMixin, FormView):
|
|
117
|
+
"""View for updating an object, with a response rendered by a template."""
|
|
118
|
+
|
|
119
|
+
template_name_suffix = "_form"
|
|
120
|
+
|
|
121
|
+
def post(self) -> Response:
|
|
122
|
+
"""
|
|
123
|
+
Handle POST requests: instantiate a form instance with the passed
|
|
124
|
+
POST variables and then check if it's valid.
|
|
125
|
+
"""
|
|
126
|
+
self.load_object()
|
|
127
|
+
return super().post()
|
|
128
|
+
|
|
129
|
+
def get_success_url(self):
|
|
130
|
+
"""Return the URL to redirect to after processing a valid form."""
|
|
131
|
+
if self.success_url:
|
|
132
|
+
url = self.success_url.format(**self.object.__dict__)
|
|
133
|
+
else:
|
|
134
|
+
try:
|
|
135
|
+
url = self.object.get_absolute_url()
|
|
136
|
+
except AttributeError:
|
|
137
|
+
raise ImproperlyConfigured(
|
|
138
|
+
"No URL to redirect to. Either provide a url or define"
|
|
139
|
+
" a get_absolute_url method on the Model."
|
|
140
|
+
)
|
|
141
|
+
return url
|
|
142
|
+
|
|
143
|
+
def form_valid(self, form):
|
|
144
|
+
"""If the form is valid, save the associated model."""
|
|
145
|
+
self.object = form.save()
|
|
146
|
+
return super().form_valid(form)
|
|
147
|
+
|
|
148
|
+
def get_form_kwargs(self):
|
|
149
|
+
"""Return the keyword arguments for instantiating the form."""
|
|
150
|
+
kwargs = super().get_form_kwargs()
|
|
151
|
+
kwargs.update({"instance": self.object})
|
|
152
|
+
return kwargs
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class DeleteView(ObjectTemplateViewMixin, TemplateView):
|
|
156
|
+
"""
|
|
157
|
+
View for deleting an object retrieved with self.get_object(), with a
|
|
158
|
+
response rendered by a template.
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
template_name_suffix = "_confirm_delete"
|
|
162
|
+
|
|
163
|
+
def get_form_kwargs(self):
|
|
164
|
+
"""Return the keyword arguments for instantiating the form."""
|
|
165
|
+
kwargs = super().get_form_kwargs()
|
|
166
|
+
kwargs.update({"instance": self.object})
|
|
167
|
+
return kwargs
|
|
168
|
+
|
|
169
|
+
def get_success_url(self):
|
|
170
|
+
if self.success_url:
|
|
171
|
+
return self.success_url.format(**self.object.__dict__)
|
|
172
|
+
else:
|
|
173
|
+
raise ImproperlyConfigured("No URL to redirect to. Provide a success_url.")
|
|
174
|
+
|
|
175
|
+
def post(self):
|
|
176
|
+
self.load_object()
|
|
177
|
+
self.object.delete()
|
|
178
|
+
return ResponseRedirect(self.get_success_url())
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class ListView(TemplateView):
|
|
182
|
+
"""
|
|
183
|
+
Render some list of objects, set by `self.get_queryset()`, with a response
|
|
184
|
+
rendered by a template.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
template_name_suffix = "_list"
|
|
188
|
+
context_object_name = "objects"
|
|
189
|
+
|
|
190
|
+
def get(self) -> Response:
|
|
191
|
+
self.objects = self.get_objects()
|
|
192
|
+
return super().get()
|
|
193
|
+
|
|
194
|
+
def get_objects(self):
|
|
195
|
+
raise NotImplementedError(
|
|
196
|
+
f"get_objects() is not implemented on {self.__class__.__name__}"
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
def get_template_context(self) -> dict:
|
|
200
|
+
"""Insert the single object into the context dict."""
|
|
201
|
+
context = super().get_template_context() # type: ignore
|
|
202
|
+
context[self.context_object_name] = self.objects
|
|
203
|
+
return context
|
|
204
|
+
|
|
205
|
+
def get_template_names(self) -> list[str]:
|
|
206
|
+
"""
|
|
207
|
+
Return a list of template names to be used for the request. May not be
|
|
208
|
+
called if render_to_response() is overridden. Return the following list:
|
|
209
|
+
|
|
210
|
+
* the value of ``template_name`` on the view (if provided)
|
|
211
|
+
object instance that the view is operating upon (if available)
|
|
212
|
+
* ``<package_label>/<model_name><template_name_suffix>.html``
|
|
213
|
+
"""
|
|
214
|
+
if self.template_name: # type: ignore
|
|
215
|
+
return [self.template_name] # type: ignore
|
|
216
|
+
|
|
217
|
+
# If template_name isn't specified, it's not a problem --
|
|
218
|
+
# we just start with an empty list.
|
|
219
|
+
names = []
|
|
220
|
+
|
|
221
|
+
# The least-specific option is the default <app>/<model>_detail.html;
|
|
222
|
+
# only use this if the object in question is a model.
|
|
223
|
+
if hasattr(self.objects, "model") and hasattr(self.objects.model, "_meta"):
|
|
224
|
+
object_meta = self.objects.model._meta
|
|
225
|
+
names.append(
|
|
226
|
+
f"{object_meta.package_label}/{object_meta.model_name}{self.template_name_suffix}.html"
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
return names
|
plain/views/redirect.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from plain.http import (
|
|
4
|
+
ResponseGone,
|
|
5
|
+
ResponsePermanentRedirect,
|
|
6
|
+
ResponseRedirect,
|
|
7
|
+
)
|
|
8
|
+
from plain.urls import reverse
|
|
9
|
+
|
|
10
|
+
from .base import View
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger("plain.request")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RedirectView(View):
|
|
16
|
+
"""Provide a redirect on any GET request."""
|
|
17
|
+
|
|
18
|
+
permanent = False
|
|
19
|
+
url: str | None = None
|
|
20
|
+
pattern_name: str | None = None
|
|
21
|
+
query_string = False
|
|
22
|
+
|
|
23
|
+
def get_redirect_url(self):
|
|
24
|
+
"""
|
|
25
|
+
Return the URL redirect to. Keyword arguments from the URL pattern
|
|
26
|
+
match generating the redirect request are provided as kwargs to this
|
|
27
|
+
method.
|
|
28
|
+
"""
|
|
29
|
+
if self.url:
|
|
30
|
+
url = self.url % self.url_kwargs
|
|
31
|
+
elif self.pattern_name:
|
|
32
|
+
url = reverse(self.pattern_name, args=self.url_args, kwargs=self.url_kwargs)
|
|
33
|
+
else:
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
args = self.request.META.get("QUERY_STRING", "")
|
|
37
|
+
if args and self.query_string:
|
|
38
|
+
url = f"{url}?{args}"
|
|
39
|
+
return url
|
|
40
|
+
|
|
41
|
+
def get(self):
|
|
42
|
+
url = self.get_redirect_url()
|
|
43
|
+
if url:
|
|
44
|
+
if self.permanent:
|
|
45
|
+
return ResponsePermanentRedirect(url)
|
|
46
|
+
else:
|
|
47
|
+
return ResponseRedirect(url)
|
|
48
|
+
else:
|
|
49
|
+
logger.warning(
|
|
50
|
+
"Gone: %s",
|
|
51
|
+
self.request.path,
|
|
52
|
+
extra={"status_code": 410, "request": self.request},
|
|
53
|
+
)
|
|
54
|
+
return ResponseGone()
|
|
55
|
+
|
|
56
|
+
def head(self):
|
|
57
|
+
return self.get()
|
|
58
|
+
|
|
59
|
+
def post(self):
|
|
60
|
+
return self.get()
|
|
61
|
+
|
|
62
|
+
def options(self):
|
|
63
|
+
return self.get()
|
|
64
|
+
|
|
65
|
+
def delete(self):
|
|
66
|
+
return self.get()
|
|
67
|
+
|
|
68
|
+
def put(self):
|
|
69
|
+
return self.get()
|
|
70
|
+
|
|
71
|
+
def patch(self):
|
|
72
|
+
return self.get()
|
plain/views/templates.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from plain.csrf.middleware import get_token
|
|
2
|
+
from plain.exceptions import ImproperlyConfigured
|
|
3
|
+
from plain.runtime import settings
|
|
4
|
+
from plain.templates import Template, TemplateFileMissing
|
|
5
|
+
from plain.utils.functional import lazy
|
|
6
|
+
from plain.utils.html import format_html
|
|
7
|
+
from plain.utils.safestring import SafeString
|
|
8
|
+
|
|
9
|
+
from .base import View
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def csrf_input(request):
|
|
13
|
+
return format_html(
|
|
14
|
+
'<input type="hidden" name="csrfmiddlewaretoken" value="{}">',
|
|
15
|
+
get_token(request),
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
csrf_input_lazy = lazy(csrf_input, SafeString, str)
|
|
20
|
+
csrf_token_lazy = lazy(get_token, str)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TemplateView(View):
|
|
24
|
+
"""
|
|
25
|
+
Render a template. Pass keyword arguments from the URLconf to the context.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
template_name: str | None = None
|
|
29
|
+
|
|
30
|
+
def get_template_context(self) -> dict:
|
|
31
|
+
return {
|
|
32
|
+
"request": self.request,
|
|
33
|
+
"csrf_input": csrf_input_lazy(self.request),
|
|
34
|
+
"csrf_token": csrf_token_lazy(self.request),
|
|
35
|
+
"DEBUG": settings.DEBUG,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
def get_template_names(self) -> list[str]:
|
|
39
|
+
"""
|
|
40
|
+
Return a list of template names to be used for the request. Must return
|
|
41
|
+
a list. May not be called if render_to_response() is overridden.
|
|
42
|
+
"""
|
|
43
|
+
if self.template_name is None:
|
|
44
|
+
raise ImproperlyConfigured(
|
|
45
|
+
"TemplateView requires either a definition of "
|
|
46
|
+
"'template_name' or an implementation of 'get_template_names()'"
|
|
47
|
+
)
|
|
48
|
+
else:
|
|
49
|
+
return [self.template_name]
|
|
50
|
+
|
|
51
|
+
def get_template(self) -> Template:
|
|
52
|
+
template_names = self.get_template_names()
|
|
53
|
+
|
|
54
|
+
for template_name in template_names:
|
|
55
|
+
try:
|
|
56
|
+
return Template(template_name)
|
|
57
|
+
except TemplateFileMissing:
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
raise TemplateFileMissing(template_names)
|
|
61
|
+
|
|
62
|
+
def render_template(self) -> str:
|
|
63
|
+
return self.get_template().render(self.get_template_context())
|
|
64
|
+
|
|
65
|
+
def get(self):
|
|
66
|
+
return self.render_template()
|
plain/wsgi.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
## Plain is released under the BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
BSD 3-Clause License
|
|
4
|
+
|
|
5
|
+
Copyright (c) 2023, Dropseed, LLC
|
|
6
|
+
|
|
7
|
+
Redistribution and use in source and binary forms, with or without
|
|
8
|
+
modification, are permitted provided that the following conditions are met:
|
|
9
|
+
|
|
10
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
11
|
+
list of conditions and the following disclaimer.
|
|
12
|
+
|
|
13
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
14
|
+
this list of conditions and the following disclaimer in the documentation
|
|
15
|
+
and/or other materials provided with the distribution.
|
|
16
|
+
|
|
17
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
18
|
+
contributors may be used to endorse or promote products derived from
|
|
19
|
+
this software without specific prior written permission.
|
|
20
|
+
|
|
21
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
22
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
23
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
24
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
25
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
26
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
27
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
28
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
29
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
30
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## This package contains code forked from github.com/django/django
|
|
34
|
+
|
|
35
|
+
Copyright (c) Django Software Foundation and individual contributors.
|
|
36
|
+
All rights reserved.
|
|
37
|
+
|
|
38
|
+
Redistribution and use in source and binary forms, with or without modification,
|
|
39
|
+
are permitted provided that the following conditions are met:
|
|
40
|
+
|
|
41
|
+
1. Redistributions of source code must retain the above copyright notice,
|
|
42
|
+
this list of conditions and the following disclaimer.
|
|
43
|
+
|
|
44
|
+
2. Redistributions in binary form must reproduce the above copyright
|
|
45
|
+
notice, this list of conditions and the following disclaimer in the
|
|
46
|
+
documentation and/or other materials provided with the distribution.
|
|
47
|
+
|
|
48
|
+
3. Neither the name of Django nor the names of its contributors may be used
|
|
49
|
+
to endorse or promote products derived from this software without
|
|
50
|
+
specific prior written permission.
|
|
51
|
+
|
|
52
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
53
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
54
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
55
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
56
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
57
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
58
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
59
|
+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
60
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
61
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
## This package contains code forked from github.com/evansd/whitenoise
|
|
65
|
+
|
|
66
|
+
The MIT License (MIT)
|
|
67
|
+
|
|
68
|
+
Copyright (c) 2013 David Evans
|
|
69
|
+
|
|
70
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
71
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
72
|
+
the Software without restriction, including without limitation the rights to
|
|
73
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
74
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
75
|
+
subject to the following conditions:
|
|
76
|
+
|
|
77
|
+
The above copyright notice and this permission notice shall be included in all
|
|
78
|
+
copies or substantial portions of the Software.
|
|
79
|
+
|
|
80
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
81
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
82
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
83
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
84
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
85
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: plain
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A web framework for building products with Python.
|
|
5
|
+
Author: Dave Gaeddert
|
|
6
|
+
Author-email: dave.gaeddert@dropseed.dev
|
|
7
|
+
Requires-Python: >=3.11,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Requires-Dist: click (>=8.0.0)
|
|
12
|
+
Requires-Dist: jinja2 (>=3.1.2,<4.0.0)
|
|
13
|
+
Requires-Dist: python-dotenv (>=1.0.0,<2.0.0)
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
<!-- This file is compiled from plain/plain/README.md. Do not edit this file directly. -->
|
|
17
|
+
|
|
18
|
+
# Plain
|
|
19
|
+
|
|
20
|
+
Plain is a web framework for building products with Python.
|
|
21
|
+
|
|
22
|
+
With the core `plain` package you can build an app that:
|
|
23
|
+
|
|
24
|
+
- Matches [URL patterns](https://plainframework.com/docs/plain/plain/urls) to Python [views](https://plainframework.com/docs/plain/plain/views)
|
|
25
|
+
- Handles [HTTP requests and responses](https://plainframework.com/docs/plain/plain/http)
|
|
26
|
+
- Renders [HTML templates](https://plainframework.com/docs/plain/plain/templates) with Jinja
|
|
27
|
+
- Processes user input via [forms](https://plainframework.com/docs/plain/plain/forms)
|
|
28
|
+
- Has a [CLI interface](https://plainframework.com/docs/plain/plain/cli)
|
|
29
|
+
- Serves static [assets](https://plainframework.com/docs/plain/plain/assets) (CSS, JS, images)
|
|
30
|
+
- Can be modified with [middleware](https://plainframework.com/docs/plain/plain/middleware)
|
|
31
|
+
- Integrates first-party and third-party [packages](https://plainframework.com/docs/plain/plain/packages)
|
|
32
|
+
- Has a [preflight check system](https://plainframework.com/docs/plain/plain/preflight)
|
|
33
|
+
|
|
34
|
+
With the official Plain ecosystem packages you can:
|
|
35
|
+
|
|
36
|
+
- Integrate a full-featured [database ORM](https://plainframework.com/docs/plain-models/)
|
|
37
|
+
- Use a built-in [user authentication](https://plainframework.com/docs/plain-auth/) system
|
|
38
|
+
- [Lint and format code](https://plainframework.com/docs/plain-code/)
|
|
39
|
+
- Run a [database-backed cache](https://plainframework.com/docs/plain-cache/)
|
|
40
|
+
- [Send emails](https://plainframework.com/docs/plain-mail/)
|
|
41
|
+
- Streamline [local development](https://plainframework.com/docs/plain-dev/)
|
|
42
|
+
- Manage [feature flags](https://plainframework.com/docs/plain-flags/)
|
|
43
|
+
- Integrate [HTMX](https://plainframework.com/docs/plain-htmx/)
|
|
44
|
+
- Style with [Tailwind CSS](https://plainframework.com/docs/plain-tailwind/)
|
|
45
|
+
- Add [OAuth login](https://plainframework.com/docs/plain-oauth/) and API access
|
|
46
|
+
- Run tests with [pytest](https://plainframework.com/docs/plain-test/)
|
|
47
|
+
- Run a [background job worker](https://plainframework.com/docs/plain-worker/)
|
|
48
|
+
- Build [staff tooling and admin dashboards](https://plainframework.com/docs/plain-staff/)
|
|
49
|
+
|
|
50
|
+
Learn more at [plainframework.com](https://plainframework.com).
|
|
51
|
+
|