plain 0.21.5__tar.gz → 0.22.1__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.
- {plain-0.21.5 → plain-0.22.1}/.gitignore +0 -3
- {plain-0.21.5 → plain-0.22.1}/PKG-INFO +1 -1
- {plain-0.21.5 → plain-0.22.1}/plain/assets/urls.py +9 -8
- {plain-0.21.5 → plain-0.22.1}/plain/assets/views.py +4 -4
- {plain-0.21.5 → plain-0.22.1}/plain/cli/cli.py +90 -7
- {plain-0.21.5 → plain-0.22.1}/plain/http/request.py +1 -2
- {plain-0.21.5 → plain-0.22.1}/plain/internal/handlers/base.py +9 -23
- {plain-0.21.5 → plain-0.22.1}/plain/internal/handlers/exception.py +2 -0
- {plain-0.21.5 → plain-0.22.1}/plain/internal/middleware/slash.py +15 -7
- {plain-0.21.5 → plain-0.22.1}/plain/packages/registry.py +3 -77
- {plain-0.21.5 → plain-0.22.1}/plain/preflight/urls.py +2 -2
- {plain-0.21.5 → plain-0.22.1}/plain/runtime/global_settings.py +1 -1
- {plain-0.21.5 → plain-0.22.1}/plain/templates/jinja/globals.py +2 -8
- {plain-0.21.5 → plain-0.22.1}/plain/test/client.py +10 -10
- {plain-0.21.5 → plain-0.22.1}/plain/urls/__init__.py +8 -19
- plain-0.22.1/plain/urls/patterns.py +271 -0
- plain-0.22.1/plain/urls/resolvers.py +408 -0
- plain-0.22.1/plain/urls/routers.py +112 -0
- plain-0.21.5/plain/urls/base.py → plain-0.22.1/plain/urls/utils.py +5 -61
- {plain-0.21.5 → plain-0.22.1}/plain/utils/functional.py +1 -2
- {plain-0.21.5 → plain-0.22.1}/plain/views/redirect.py +1 -1
- {plain-0.21.5 → plain-0.22.1}/plain/views/templates.py +1 -1
- {plain-0.21.5 → plain-0.22.1}/pyproject.toml +1 -1
- plain-0.22.1/tests/app/urls.py +14 -0
- plain-0.21.5/plain/urls/conf.py +0 -95
- plain-0.21.5/plain/urls/resolvers.py +0 -718
- plain-0.21.5/plain/utils/deprecation.py +0 -6
- plain-0.21.5/tests/app/urls.py +0 -12
- plain-0.21.5/uv.lock +0 -148
- {plain-0.21.5 → plain-0.22.1}/LICENSE +0 -0
- {plain-0.21.5 → plain-0.22.1}/README.md +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/README.md +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/__main__.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/assets/README.md +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/assets/__init__.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/assets/compile.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/assets/finders.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/assets/fingerprints.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/cli/README.md +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/cli/__init__.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/cli/formatting.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/cli/packages.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/cli/print.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/cli/startup.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/csrf/README.md +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/csrf/middleware.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/csrf/views.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/debug.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/exceptions.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/forms/README.md +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/forms/__init__.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/forms/boundfield.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/forms/exceptions.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/forms/fields.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/forms/forms.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/http/README.md +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/http/__init__.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/http/cookie.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/http/multipartparser.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/http/response.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/internal/__init__.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/internal/files/README.md +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/internal/files/__init__.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/internal/files/base.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/internal/files/locks.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/internal/files/move.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/internal/files/temp.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/internal/files/uploadedfile.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/internal/files/uploadhandler.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/internal/files/utils.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/internal/handlers/__init__.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/internal/handlers/wsgi.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/internal/middleware/__init__.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/internal/middleware/headers.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/internal/middleware/https.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/json.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/logs/README.md +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/logs/__init__.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/logs/configure.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/logs/loggers.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/logs/utils.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/packages/README.md +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/packages/__init__.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/packages/config.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/paginator.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/preflight/README.md +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/preflight/__init__.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/preflight/files.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/preflight/messages.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/preflight/registry.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/preflight/security.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/runtime/README.md +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/runtime/__init__.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/runtime/user_settings.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/signals/README.md +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/signals/__init__.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/signals/dispatch/__init__.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/signals/dispatch/dispatcher.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/signals/dispatch/license.txt +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/signing.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/templates/README.md +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/templates/__init__.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/templates/core.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/templates/jinja/README.md +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/templates/jinja/__init__.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/templates/jinja/environments.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/templates/jinja/extensions.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/templates/jinja/filters.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/test/README.md +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/test/__init__.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/urls/README.md +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/urls/converters.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/urls/exceptions.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/README.md +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/__init__.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/_os.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/cache.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/connection.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/crypto.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/datastructures.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/dateformat.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/dateparse.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/dates.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/deconstruct.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/decorators.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/duration.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/email.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/encoding.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/hashable.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/html.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/http.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/inspect.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/ipv6.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/itercompat.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/module_loading.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/regex_helper.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/safestring.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/text.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/timesince.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/timezone.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/utils/tree.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/validators.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/views/README.md +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/views/__init__.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/views/base.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/views/csrf.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/views/errors.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/views/exceptions.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/views/forms.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/views/objects.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/plain/wsgi.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/tests/.bolt/assets_collected/assets.json +0 -0
- {plain-0.21.5 → plain-0.22.1}/tests/.gitignore +0 -0
- {plain-0.21.5 → plain-0.22.1}/tests/app/.gitignore +0 -0
- {plain-0.21.5 → plain-0.22.1}/tests/app/settings.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/tests/app/test/__init__.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/tests/app/test/default_settings.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/tests/conftest.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/tests/test_cli.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/tests/test_runtime.py +0 -0
- {plain-0.21.5 → plain-0.22.1}/tests/test_wsgi.py +0 -0
@@ -1,10 +1,16 @@
|
|
1
1
|
from plain.runtime import settings
|
2
|
-
from plain.urls import path, reverse
|
2
|
+
from plain.urls import RouterBase, path, register_router, reverse
|
3
3
|
|
4
4
|
from .fingerprints import get_fingerprinted_url_path
|
5
5
|
from .views import AssetView
|
6
6
|
|
7
|
-
|
7
|
+
|
8
|
+
@register_router
|
9
|
+
class Router(RouterBase):
|
10
|
+
namespace = "assets"
|
11
|
+
urls = [
|
12
|
+
path("<path:path>", AssetView, name="asset"),
|
13
|
+
]
|
8
14
|
|
9
15
|
|
10
16
|
def get_asset_url(url_path):
|
@@ -23,9 +29,4 @@ def get_asset_url(url_path):
|
|
23
29
|
if settings.ASSETS_BASE_URL:
|
24
30
|
return settings.ASSETS_BASE_URL + resolved_url_path
|
25
31
|
|
26
|
-
return reverse(
|
27
|
-
|
28
|
-
|
29
|
-
urlpatterns = [
|
30
|
-
path("<path:path>", AssetView, name="asset"),
|
31
|
-
]
|
32
|
+
return reverse(Router.namespace + ":asset", path=resolved_url_path)
|
@@ -206,12 +206,12 @@ class AssetView(View):
|
|
206
206
|
# or we're already looking at it.
|
207
207
|
return
|
208
208
|
|
209
|
-
from .urls import
|
209
|
+
from .urls import Router
|
210
|
+
|
211
|
+
namespace = Router.namespace
|
210
212
|
|
211
213
|
return ResponseRedirect(
|
212
|
-
redirect_to=reverse(
|
213
|
-
f"{default_namespace}:asset", args=[fingerprinted_url_path]
|
214
|
-
),
|
214
|
+
redirect_to=reverse(f"{namespace}:asset", fingerprinted_url_path),
|
215
215
|
headers={
|
216
216
|
"Cache-Control": "max-age=60", # Can cache this for a short time, but the fingerprinted path can change
|
217
217
|
},
|
@@ -346,6 +346,9 @@ def compile(keep_original, fingerprint, compress):
|
|
346
346
|
f"\nCompiled {total_files} assets into {total_compiled} files", fg="green"
|
347
347
|
)
|
348
348
|
|
349
|
+
# TODO could do a jinja pre-compile here too?
|
350
|
+
# environment.compile_templates() but it needs a target, ignore_errors=False
|
351
|
+
|
349
352
|
|
350
353
|
@plain_cli.command()
|
351
354
|
@click.argument("package_name")
|
@@ -378,13 +381,14 @@ def create(package_name):
|
|
378
381
|
# Create a urls.py file with a default namespace
|
379
382
|
if not (package_dir / "urls.py").exists():
|
380
383
|
(package_dir / "urls.py").write_text(
|
381
|
-
f"""from plain.urls import path
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
384
|
+
f"""from plain.urls import path, RouterBase, register_router
|
385
|
+
|
386
|
+
@register_router
|
387
|
+
class Router(RouterBase):
|
388
|
+
namespace = f"{package_name}"
|
389
|
+
urls = [
|
390
|
+
# path("", views.IndexView, name="index"),
|
391
|
+
]
|
388
392
|
"""
|
389
393
|
)
|
390
394
|
|
@@ -417,6 +421,85 @@ def generate_secret_key():
|
|
417
421
|
click.echo(new_secret_key)
|
418
422
|
|
419
423
|
|
424
|
+
@plain_cli.command()
|
425
|
+
@click.option("--flat", is_flag=True, help="List all URLs in a flat list")
|
426
|
+
def urls(flat):
|
427
|
+
"""Print all URL patterns under settings.URLS_MODULE"""
|
428
|
+
from plain.urls import URLResolver, get_resolver
|
429
|
+
|
430
|
+
resolver = get_resolver()
|
431
|
+
if flat:
|
432
|
+
|
433
|
+
def flat_list(patterns, prefix="", curr_ns=""):
|
434
|
+
for pattern in patterns:
|
435
|
+
full_pattern = f"{prefix}{pattern.pattern}"
|
436
|
+
if isinstance(pattern, URLResolver):
|
437
|
+
# Update current namespace
|
438
|
+
new_ns = (
|
439
|
+
f"{curr_ns}:{pattern.namespace}"
|
440
|
+
if curr_ns and pattern.namespace
|
441
|
+
else (pattern.namespace or curr_ns)
|
442
|
+
)
|
443
|
+
yield from flat_list(
|
444
|
+
pattern.url_patterns, prefix=full_pattern, curr_ns=new_ns
|
445
|
+
)
|
446
|
+
else:
|
447
|
+
if pattern.name:
|
448
|
+
if curr_ns:
|
449
|
+
styled_namespace = click.style(f"{curr_ns}:", fg="yellow")
|
450
|
+
styled_name = click.style(pattern.name, fg="blue")
|
451
|
+
full_name = f"{styled_namespace}{styled_name}"
|
452
|
+
else:
|
453
|
+
full_name = click.style(pattern.name, fg="blue")
|
454
|
+
name_part = f" [{full_name}]"
|
455
|
+
else:
|
456
|
+
name_part = ""
|
457
|
+
yield f"{click.style(full_pattern)}{name_part}"
|
458
|
+
|
459
|
+
for p in flat_list(resolver.url_patterns):
|
460
|
+
click.echo(p)
|
461
|
+
else:
|
462
|
+
|
463
|
+
def print_tree(patterns, prefix="", curr_ns=""):
|
464
|
+
count = len(patterns)
|
465
|
+
for idx, pattern in enumerate(patterns):
|
466
|
+
is_last = idx == (count - 1)
|
467
|
+
connector = "└── " if is_last else "├── "
|
468
|
+
styled_connector = click.style(connector)
|
469
|
+
styled_pattern = click.style(pattern.pattern)
|
470
|
+
if isinstance(pattern, URLResolver):
|
471
|
+
if pattern.namespace:
|
472
|
+
new_ns = (
|
473
|
+
f"{curr_ns}:{pattern.namespace}"
|
474
|
+
if curr_ns
|
475
|
+
else pattern.namespace
|
476
|
+
)
|
477
|
+
styled_namespace = click.style(f"[{new_ns}]", fg="yellow")
|
478
|
+
click.echo(
|
479
|
+
f"{prefix}{styled_connector}{styled_pattern} {styled_namespace}"
|
480
|
+
)
|
481
|
+
else:
|
482
|
+
new_ns = curr_ns
|
483
|
+
click.echo(f"{prefix}{styled_connector}{styled_pattern}")
|
484
|
+
extension = " " if is_last else "│ "
|
485
|
+
print_tree(pattern.url_patterns, prefix + extension, new_ns)
|
486
|
+
else:
|
487
|
+
if pattern.name:
|
488
|
+
if curr_ns:
|
489
|
+
styled_namespace = click.style(f"{curr_ns}:", fg="yellow")
|
490
|
+
styled_name = click.style(pattern.name, fg="blue")
|
491
|
+
full_name = f"[{styled_namespace}{styled_name}]"
|
492
|
+
else:
|
493
|
+
full_name = click.style(f"[{pattern.name}]", fg="blue")
|
494
|
+
click.echo(
|
495
|
+
f"{prefix}{styled_connector}{styled_pattern} {full_name}"
|
496
|
+
)
|
497
|
+
else:
|
498
|
+
click.echo(f"{prefix}{styled_connector}{styled_pattern}")
|
499
|
+
|
500
|
+
print_tree(resolver.url_patterns)
|
501
|
+
|
502
|
+
|
420
503
|
class AppCLIGroup(click.Group):
|
421
504
|
"""
|
422
505
|
Loads app.cli if it exists as `plain app`
|
@@ -328,8 +328,7 @@ class HttpRequest:
|
|
328
328
|
self.upload_handlers = ImmutableList(
|
329
329
|
self.upload_handlers,
|
330
330
|
warning=(
|
331
|
-
"You cannot alter upload handlers after the upload has been "
|
332
|
-
"processed."
|
331
|
+
"You cannot alter upload handlers after the upload has been processed."
|
333
332
|
),
|
334
333
|
)
|
335
334
|
parser = MultiPartParser(META, post_data, self.upload_handlers, self.encoding)
|
@@ -4,8 +4,7 @@ import types
|
|
4
4
|
from plain.exceptions import ImproperlyConfigured
|
5
5
|
from plain.logs import log_response
|
6
6
|
from plain.runtime import settings
|
7
|
-
from plain.
|
8
|
-
from plain.urls import get_resolver, set_urlconf
|
7
|
+
from plain.urls import get_resolver
|
9
8
|
from plain.utils.module_loading import import_string
|
10
9
|
|
11
10
|
from .exception import convert_exception_to_response
|
@@ -61,7 +60,6 @@ class BaseHandler:
|
|
61
60
|
def get_response(self, request):
|
62
61
|
"""Return a Response object for the given HttpRequest."""
|
63
62
|
# Setup default url resolver for this thread
|
64
|
-
set_urlconf(settings.ROOT_URLCONF)
|
65
63
|
response = self._middleware_chain(request)
|
66
64
|
response._resource_closers.append(request.close)
|
67
65
|
if response.status_code >= 400:
|
@@ -80,27 +78,23 @@ class BaseHandler:
|
|
80
78
|
template_response middleware. This method is everything that happens
|
81
79
|
inside the request/response middleware.
|
82
80
|
"""
|
83
|
-
|
81
|
+
resolver_match = self.resolve_request(request)
|
84
82
|
|
85
|
-
response =
|
83
|
+
response = resolver_match.func(
|
84
|
+
request, *resolver_match.args, **resolver_match.kwargs
|
85
|
+
)
|
86
86
|
|
87
87
|
# Complain if the view returned None (a common error).
|
88
|
-
self.check_response(response,
|
88
|
+
self.check_response(response, resolver_match.func)
|
89
89
|
|
90
90
|
return response
|
91
91
|
|
92
92
|
def resolve_request(self, request):
|
93
93
|
"""
|
94
|
-
Retrieve/set the
|
94
|
+
Retrieve/set the urlrouter for the request. Return the view resolved,
|
95
95
|
with its args and kwargs.
|
96
96
|
"""
|
97
|
-
|
98
|
-
if hasattr(request, "urlconf"):
|
99
|
-
urlconf = request.urlconf
|
100
|
-
set_urlconf(urlconf)
|
101
|
-
resolver = get_resolver(urlconf)
|
102
|
-
else:
|
103
|
-
resolver = get_resolver()
|
97
|
+
resolver = get_resolver()
|
104
98
|
# Resolve the view, and assign the match object back to the request.
|
105
99
|
resolver_match = resolver.resolve(request.path_info)
|
106
100
|
request.resolver_match = resolver_match
|
@@ -117,13 +111,5 @@ class BaseHandler:
|
|
117
111
|
name = f"The view {callback.__module__}.{callback.__class__.__name__}.__call__"
|
118
112
|
if response is None:
|
119
113
|
raise ValueError(
|
120
|
-
f"{name} didn't return a Response object. It returned None
|
114
|
+
f"{name} didn't return a Response object. It returned None instead."
|
121
115
|
)
|
122
|
-
|
123
|
-
|
124
|
-
def reset_urlconf(sender, **kwargs):
|
125
|
-
"""Reset the URLconf after each request is finished."""
|
126
|
-
set_urlconf(None)
|
127
|
-
|
128
|
-
|
129
|
-
request_finished.connect(reset_urlconf)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
from plain.http import ResponsePermanentRedirect
|
2
2
|
from plain.runtime import settings
|
3
|
-
from plain.urls import
|
3
|
+
from plain.urls import Resolver404, get_resolver
|
4
4
|
from plain.utils.http import escape_leading_slashes
|
5
5
|
|
6
6
|
|
@@ -26,18 +26,26 @@ class RedirectSlashMiddleware:
|
|
26
26
|
|
27
27
|
return response
|
28
28
|
|
29
|
+
@staticmethod
|
30
|
+
def _is_valid_path(path):
|
31
|
+
"""
|
32
|
+
Return the ResolverMatch if the given path resolves against the default URL
|
33
|
+
resolver, False otherwise. This is a convenience method to make working
|
34
|
+
with "is this a match?" cases easier, avoiding try...except blocks.
|
35
|
+
"""
|
36
|
+
try:
|
37
|
+
return get_resolver().resolve(path)
|
38
|
+
except Resolver404:
|
39
|
+
return False
|
40
|
+
|
29
41
|
def should_redirect_with_slash(self, request):
|
30
42
|
"""
|
31
43
|
Return True if settings.APPEND_SLASH is True and appending a slash to
|
32
44
|
the request path turns an invalid path into a valid one.
|
33
45
|
"""
|
34
46
|
if settings.APPEND_SLASH and not request.path_info.endswith("/"):
|
35
|
-
|
36
|
-
|
37
|
-
match = is_valid_path(f"{request.path_info}/", urlconf)
|
38
|
-
if match:
|
39
|
-
view = match.func
|
40
|
-
return getattr(view, "should_append_slash", True)
|
47
|
+
if not self._is_valid_path(request.path_info):
|
48
|
+
return self._is_valid_path(f"{request.path_info}/")
|
41
49
|
return False
|
42
50
|
|
43
51
|
def get_full_path_with_slash(self, request):
|
@@ -24,8 +24,8 @@ class Packages:
|
|
24
24
|
if installed_packages is None and hasattr(sys.modules[__name__], "packages"):
|
25
25
|
raise RuntimeError("You must supply an installed_packages argument.")
|
26
26
|
|
27
|
-
# Mapping of app labels => model names => model classes.
|
28
|
-
#
|
27
|
+
# Mapping of app labels => model names => model classes.
|
28
|
+
# Models are registered with @models.register_model, which
|
29
29
|
# creates an entry in all_models. All imported models are registered,
|
30
30
|
# regardless of whether they're defined in an installed application
|
31
31
|
# and whether the registry has been populated. Since it isn't possible
|
@@ -36,10 +36,6 @@ class Packages:
|
|
36
36
|
# Mapping of labels to PackageConfig instances for installed packages.
|
37
37
|
self.package_configs = {}
|
38
38
|
|
39
|
-
# Stack of package_configs. Used to store the current state in
|
40
|
-
# set_available_packages and set_installed_packages.
|
41
|
-
self.stored_package_configs = []
|
42
|
-
|
43
39
|
# Whether the registry is populated.
|
44
40
|
self.packages_ready = self.models_ready = self.ready = False
|
45
41
|
|
@@ -103,7 +99,7 @@ class Packages:
|
|
103
99
|
duplicates = [name for name, count in counts.most_common() if count > 1]
|
104
100
|
if duplicates:
|
105
101
|
raise ImproperlyConfigured(
|
106
|
-
"Package names aren't unique,
|
102
|
+
"Package names aren't unique, duplicates: {}".format(
|
107
103
|
", ".join(duplicates)
|
108
104
|
)
|
109
105
|
)
|
@@ -238,15 +234,6 @@ class Packages:
|
|
238
234
|
self.do_pending_operations(model)
|
239
235
|
self.clear_cache()
|
240
236
|
|
241
|
-
def is_installed(self, package_name):
|
242
|
-
"""
|
243
|
-
Check whether an application with this name exists in the registry.
|
244
|
-
|
245
|
-
package_name is the full name of the app e.g. 'plain.admin'.
|
246
|
-
"""
|
247
|
-
self.check_packages_ready()
|
248
|
-
return any(ac.name == package_name for ac in self.package_configs.values())
|
249
|
-
|
250
237
|
def get_containing_package_config(self, object_name):
|
251
238
|
"""
|
252
239
|
Look for an app config containing a given object.
|
@@ -302,67 +289,6 @@ class Packages:
|
|
302
289
|
return model._meta.swappable
|
303
290
|
return None
|
304
291
|
|
305
|
-
def set_available_packages(self, available):
|
306
|
-
"""
|
307
|
-
Restrict the set of installed packages used by get_package_config[s].
|
308
|
-
|
309
|
-
available must be an iterable of application names.
|
310
|
-
|
311
|
-
set_available_packages() must be balanced with unset_available_packages().
|
312
|
-
|
313
|
-
Primarily used for performance optimization in TransactionTestCase.
|
314
|
-
|
315
|
-
This method is safe in the sense that it doesn't trigger any imports.
|
316
|
-
"""
|
317
|
-
available = set(available)
|
318
|
-
installed = {
|
319
|
-
package_config.name for package_config in self.get_package_configs()
|
320
|
-
}
|
321
|
-
if not available.issubset(installed):
|
322
|
-
raise ValueError(
|
323
|
-
"Available packages isn't a subset of installed packages, extra packages: {}".format(
|
324
|
-
", ".join(available - installed)
|
325
|
-
)
|
326
|
-
)
|
327
|
-
|
328
|
-
self.stored_package_configs.append(self.package_configs)
|
329
|
-
self.package_configs = {
|
330
|
-
label: package_config
|
331
|
-
for label, package_config in self.package_configs.items()
|
332
|
-
if package_config.name in available
|
333
|
-
}
|
334
|
-
self.clear_cache()
|
335
|
-
|
336
|
-
def unset_available_packages(self):
|
337
|
-
"""Cancel a previous call to set_available_packages()."""
|
338
|
-
self.package_configs = self.stored_package_configs.pop()
|
339
|
-
self.clear_cache()
|
340
|
-
|
341
|
-
def set_installed_packages(self, installed):
|
342
|
-
"""
|
343
|
-
Enable a different set of installed packages for get_package_config[s].
|
344
|
-
|
345
|
-
installed must be an iterable in the same format as INSTALLED_PACKAGES.
|
346
|
-
|
347
|
-
set_installed_packages() must be balanced with unset_installed_packages(),
|
348
|
-
even if it exits with an exception.
|
349
|
-
|
350
|
-
Primarily used as a receiver of the setting_changed signal in tests.
|
351
|
-
|
352
|
-
This method may trigger new imports, which may add new models to the
|
353
|
-
registry of all imported models. They will stay in the registry even
|
354
|
-
after unset_installed_packages(). Since it isn't possible to replay
|
355
|
-
imports safely (e.g. that could lead to registering listeners twice),
|
356
|
-
models are registered when they're imported and never removed.
|
357
|
-
"""
|
358
|
-
if not self.ready:
|
359
|
-
raise PackageRegistryNotReady("Package registry isn't ready yet.")
|
360
|
-
self.stored_package_configs.append(self.package_configs)
|
361
|
-
self.package_configs = {}
|
362
|
-
self.packages_ready = self.models_ready = self.loading = self.ready = False
|
363
|
-
self.clear_cache()
|
364
|
-
self.populate(installed)
|
365
|
-
|
366
292
|
def clear_cache(self):
|
367
293
|
"""
|
368
294
|
Clear all internal caches, for methods that alter the app registry.
|
@@ -7,7 +7,7 @@ from . import Error, Warning, register
|
|
7
7
|
|
8
8
|
@register
|
9
9
|
def check_url_config(package_configs, **kwargs):
|
10
|
-
if getattr(settings, "
|
10
|
+
if getattr(settings, "URLS_MODULE", None):
|
11
11
|
from plain.urls import get_resolver
|
12
12
|
|
13
13
|
resolver = get_resolver()
|
@@ -33,7 +33,7 @@ def check_url_namespaces_unique(package_configs, **kwargs):
|
|
33
33
|
"""
|
34
34
|
Warn if URL namespaces used in applications aren't unique.
|
35
35
|
"""
|
36
|
-
if not getattr(settings, "
|
36
|
+
if not getattr(settings, "URLS_MODULE", None):
|
37
37
|
return []
|
38
38
|
|
39
39
|
from plain.urls import get_resolver
|
@@ -1,14 +1,8 @@
|
|
1
1
|
from plain.paginator import Paginator
|
2
|
+
from plain.urls import reverse
|
2
3
|
from plain.utils import timezone
|
3
4
|
|
4
5
|
|
5
|
-
def url(viewname, *args, **kwargs):
|
6
|
-
# A modified reverse that lets you pass args directly, excluding urlconf
|
7
|
-
from plain.urls import reverse
|
8
|
-
|
9
|
-
return reverse(viewname, args=args, kwargs=kwargs)
|
10
|
-
|
11
|
-
|
12
6
|
def asset(url_path):
|
13
7
|
# An explicit callable we can control, but also delay the import of asset.urls->views->templates
|
14
8
|
# for circular import reasons
|
@@ -19,7 +13,7 @@ def asset(url_path):
|
|
19
13
|
|
20
14
|
default_globals = {
|
21
15
|
"asset": asset,
|
22
|
-
"url":
|
16
|
+
"url": reverse,
|
23
17
|
"Paginator": Paginator,
|
24
18
|
"now": timezone.now,
|
25
19
|
"localtime": timezone.localtime,
|
@@ -15,7 +15,7 @@ from plain.internal.handlers.wsgi import WSGIRequest
|
|
15
15
|
from plain.json import PlainJSONEncoder
|
16
16
|
from plain.runtime import settings
|
17
17
|
from plain.signals import got_request_exception, request_started
|
18
|
-
from plain.urls import
|
18
|
+
from plain.urls import get_resolver
|
19
19
|
from plain.utils.encoding import force_bytes
|
20
20
|
from plain.utils.functional import SimpleLazyObject
|
21
21
|
from plain.utils.http import urlencode
|
@@ -107,9 +107,9 @@ class FakePayload(IOBase):
|
|
107
107
|
self.read_started = True
|
108
108
|
if size == -1 or size is None:
|
109
109
|
size = self.__len
|
110
|
-
assert (
|
111
|
-
|
112
|
-
)
|
110
|
+
assert self.__len >= size, (
|
111
|
+
"Cannot read more than the available bytes from the HTTP incoming data."
|
112
|
+
)
|
113
113
|
content = self.__content.read(size)
|
114
114
|
self.__len -= len(content)
|
115
115
|
return content
|
@@ -120,9 +120,9 @@ class FakePayload(IOBase):
|
|
120
120
|
self.read_started = True
|
121
121
|
if size == -1 or size is None:
|
122
122
|
size = self.__len
|
123
|
-
assert (
|
124
|
-
|
125
|
-
)
|
123
|
+
assert self.__len >= size, (
|
124
|
+
"Cannot read more than the available bytes from the HTTP incoming data."
|
125
|
+
)
|
126
126
|
content = self.__content.readline(size)
|
127
127
|
self.__len -= len(content)
|
128
128
|
return content
|
@@ -298,7 +298,7 @@ class RequestFactory:
|
|
298
298
|
post_request = rf.post('/submit/', {'foo': 'bar'})
|
299
299
|
|
300
300
|
Once you have a request object you can pass it to any view function,
|
301
|
-
just as if that view had been hooked up using a
|
301
|
+
just as if that view had been hooked up using a urlrouter.
|
302
302
|
"""
|
303
303
|
|
304
304
|
def __init__(self, *, json_encoder=PlainJSONEncoder, headers=None, **defaults):
|
@@ -690,9 +690,9 @@ class Client(ClientMixin, RequestFactory):
|
|
690
690
|
response.user = response.wsgi_request.user
|
691
691
|
|
692
692
|
# Attach the ResolverMatch instance to the response.
|
693
|
-
|
693
|
+
resolver = get_resolver()
|
694
694
|
response.resolver_match = SimpleLazyObject(
|
695
|
-
lambda: resolve(request["PATH_INFO"]
|
695
|
+
lambda: resolver.resolve(request["PATH_INFO"]),
|
696
696
|
)
|
697
697
|
|
698
698
|
# Update persistent cookie data.
|
@@ -1,22 +1,16 @@
|
|
1
|
-
from .base import (
|
2
|
-
clear_url_caches,
|
3
|
-
get_urlconf,
|
4
|
-
is_valid_path,
|
5
|
-
resolve,
|
6
|
-
reverse,
|
7
|
-
reverse_lazy,
|
8
|
-
set_urlconf,
|
9
|
-
)
|
10
|
-
from .conf import include, path, re_path
|
11
1
|
from .converters import register_converter
|
12
2
|
from .exceptions import NoReverseMatch, Resolver404
|
3
|
+
from .patterns import URLPattern
|
13
4
|
from .resolvers import (
|
14
5
|
ResolverMatch,
|
15
|
-
URLPattern,
|
16
6
|
URLResolver,
|
17
|
-
get_ns_resolver,
|
18
7
|
get_resolver,
|
19
8
|
)
|
9
|
+
from .routers import RouterBase, include, path, register_router
|
10
|
+
from .utils import (
|
11
|
+
reverse,
|
12
|
+
reverse_lazy,
|
13
|
+
)
|
20
14
|
|
21
15
|
__all__ = [
|
22
16
|
"NoReverseMatch",
|
@@ -24,17 +18,12 @@ __all__ = [
|
|
24
18
|
"URLResolver",
|
25
19
|
"Resolver404",
|
26
20
|
"ResolverMatch",
|
27
|
-
"clear_url_caches",
|
28
|
-
"get_ns_resolver",
|
29
21
|
"get_resolver",
|
30
|
-
"get_urlconf",
|
31
22
|
"include",
|
32
|
-
"is_valid_path",
|
33
23
|
"path",
|
34
|
-
"re_path",
|
35
24
|
"register_converter",
|
36
|
-
"resolve",
|
37
25
|
"reverse",
|
38
26
|
"reverse_lazy",
|
39
|
-
"
|
27
|
+
"RouterBase",
|
28
|
+
"register_router",
|
40
29
|
]
|