-
-
Notifications
You must be signed in to change notification settings - Fork 800
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support URLRouter with include #2110
base: main
Are you sure you want to change the base?
Changes from all commits
cc4a598
98de0a2
5309e68
b9b0c76
436d38a
4b47981
1890cd8
9d4feeb
504bd3c
76260f7
7ca0037
4df1d5f
cdc8c6c
e35c2d5
e3e411f
3376bb2
88ddd77
758a205
dd4b1aa
29c7402
74e5ee2
05a869a
6773674
a303572
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,10 @@ | ||
import importlib | ||
import re | ||
|
||
from django.conf import settings | ||
from django.core.exceptions import ImproperlyConfigured | ||
from django.urls.exceptions import Resolver404 | ||
from django.urls.resolvers import RegexPattern, RoutePattern, URLResolver | ||
from django.urls.resolvers import RegexPattern, RoutePattern, URLPattern, URLResolver | ||
|
||
""" | ||
All Routing instances inside this file are also valid ASGI applications - with | ||
|
@@ -52,6 +53,70 @@ async def __call__(self, scope, receive, send): | |
) | ||
|
||
|
||
def _parse_resolver(child_url_pattern, parent_resolver, parent_regex, routes): | ||
"""Parse resolver (returned by `include`) recurrsively | ||
|
||
Parameters | ||
---------- | ||
child_url_pattern : URLResolver | | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why the trailing |
||
The child url pattern | ||
parent_resolver : URLResolver | ||
The parent resolver | ||
parent_regex : Pattern | ||
The parent regex pattern | ||
routes : list[URLPattern] | ||
The URLPattern's list that stores the routes | ||
|
||
Returns | ||
------- | ||
list[URLPattern] | ||
The URLPattern's list that stores the routes | ||
""" | ||
if isinstance(child_url_pattern, URLResolver): | ||
# parse the urls resolved by django's `include` function | ||
for url_pattern in child_url_pattern.url_patterns: | ||
# call _parse_resolver recurrsively to parse nested URLResolver | ||
routes.extend( | ||
_parse_resolver( | ||
url_pattern, | ||
child_url_pattern, | ||
parent_resolver.pattern.regex, | ||
routes, | ||
) | ||
) | ||
else: | ||
# concatenate parent's url (route) and child's url (url_pattern) | ||
regex = "".join( | ||
x.pattern | ||
for x in [ | ||
parent_regex, | ||
parent_resolver.pattern.regex, | ||
child_url_pattern.pattern.regex, | ||
] | ||
) | ||
|
||
# Remove the redundant caret ^ which is appended by `path` function | ||
regex = re.sub(r"(?<!^)\^", "", regex) | ||
|
||
name = ( | ||
f"{parent_resolver.app_name}:{child_url_pattern.name}" | ||
if child_url_pattern.name | ||
else None | ||
) | ||
pattern = RegexPattern(regex, name=name, is_endpoint=True) | ||
|
||
routes.append( | ||
URLPattern( | ||
pattern, | ||
child_url_pattern.callback, | ||
child_url_pattern.default_args, | ||
name, | ||
) | ||
) | ||
|
||
return routes | ||
|
||
|
||
class URLRouter: | ||
""" | ||
Routes to different applications/consumers based on the URL path. | ||
|
@@ -67,7 +132,17 @@ class URLRouter: | |
_path_routing = True | ||
|
||
def __init__(self, routes): | ||
self.routes = routes | ||
new_routes = [] | ||
for route in routes: | ||
if not route.callback and isinstance(route, URLResolver): | ||
# parse the urls resolved by django's `include` function | ||
for url_pattern in route.url_patterns: | ||
new_routes.extend( | ||
_parse_resolver(url_pattern, route, re.compile(r""), []) | ||
) | ||
else: | ||
new_routes.append(route) | ||
self.routes = new_routes | ||
|
||
for route in self.routes: | ||
# The inner ASGI app wants to do additional routing, route | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -103,10 +103,29 @@ would do this: | |||||
|
||||||
stream = self.scope["url_route"]["kwargs"]["stream"] | ||||||
|
||||||
Please note that ``URLRouter`` nesting will not work properly with | ||||||
``path()`` routes if inner routers are wrapped by additional middleware. | ||||||
See `Issue #1428 <https://github.com/django/channels/issues/1428>`__. | ||||||
|
||||||
You can use [include](https://docs.djangoproject.com/en/5.1/ref/urls/#include) | ||||||
function for nested routings. This is similar as Django's URL routing system. | ||||||
|
||||||
Here's an example for nested routings. When you configure the routings in parent ``routings.py``; | ||||||
|
||||||
.. code-block:: python | ||||||
|
||||||
urlpatterns = [ | ||||||
path("app1/", include("src.app1.routings"), name="app1"), | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
The |
||||||
] | ||||||
|
||||||
and in child ``app1/routings.py``; | ||||||
|
||||||
.. code-block:: python | ||||||
|
||||||
app_name = 'app1' | ||||||
|
||||||
urlpatterns = [ | ||||||
re_path(r"chats/(\d+)/$", test_app, name="chats"), | ||||||
] | ||||||
|
||||||
you can establish the connection via the path such like ``/app1/chats/5/``. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
ChannelNameRouter | ||||||
----------------- | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,5 @@ | ||
import sys | ||
|
||
import pytest | ||
from django.conf import settings | ||
|
||
|
@@ -38,3 +40,14 @@ def samesite(request, settings): | |
def samesite_invalid(settings): | ||
"""Set samesite flag to strict.""" | ||
settings.SESSION_COOKIE_SAMESITE = "Hello" | ||
|
||
|
||
@pytest.fixture(autouse=True) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not keen on the |
||
def mock_modules(): | ||
"""Save original modules for each test and clear a cache""" | ||
original_modules = sys.modules.copy() | ||
yield | ||
sys.modules = original_modules | ||
from django.urls.base import _get_cached_resolver | ||
|
||
_get_cached_resolver.cache_clear() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.