-
-
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?
Conversation
Would you mind fixing the lint errors? |
I've modified codes by ruff. Unfortunately, I coundn't run linter ( |
Hey @jjjkkkjjj. I've done some more reading and thinking on this topic and I'm not sure this is correct. Per the docs:
https://channels.readthedocs.io/en/latest/releases/2.1.0.html#nested-url-routing And per this stack overflow question there is a clean (and channels native) way to do this: https://stackoverflow.com/questions/56239070/how-can-i-nest-urlrouter-in-django-channels I think that we won't move forward with this PR since an alternative, preferred technique exists. |
Yes, that's why I modified it in this PR. Actually, the unit test has passed. I think the workaround solution is valid too, but my PR has a merit that can use But I don't understand django's url routing system completely. When you judge this PR can't be accepted, I'll close this PR. |
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.
but my PR has a merit that can use reverse function.
Oh interesting, I didn't realize the current solution doesn't allow reverse
. Good call out!
Alright, I think this is a good idea then.
Can you add some test cases for reverse
?
And it looks like there are still lint errors: https://github.com/django/channels/actions/runs/9875832061/job/27301864730?pr=2110
(and see my other inline questions/requests, this PR is looking pretty reasonable. We just need to tighten up a few things)
channels/routing.py
Outdated
x.pattern | ||
for x in [route.pattern.regex, url_pattern.pattern.regex] | ||
) | ||
regex = re.sub(r"(/)\1+", r"\1", regex) |
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.
Can you help me understand the purpose of this line? Is it intended to handle the case where route.pattern.regex
ends with a /
AND url_pattern.pattern.regex
starts with a /
?
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.
Is it intended to handle the case where route.pattern.regex ends with a / AND url_pattern.pattern.regex starts with a /?
No, this is intended to remove the sequential '/'.
For example, when the url is '//s///s', the output will be '/s/s'
>>> re.sub(r"(/)\1+", r"\1", '///s//s')
'/s/s'
I've left the comment about this by 7ca0037
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.
Why is this needed? Can users just configure their urlpatterns to avoid this?
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.
Yes, we can avoid this when we configure the urlpatterns like this;
Case;
- parent
urlpatterns = [
path("universe/", include("__src.universe.routings"), name="universe"),
]
- child
urlpatterns = [
path("home/", test_app, name="home"),
]
This is intended for escaping the unnecessary /
. This code allows us to configure the urlpatterns even if we configure them like this;
- child
urlpatterns = [
path("/home/", test_app, name="home"),
^----- here!
]
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.
I removed it for now by 758a205
@bigfootjon But, I could fix some bugs and implemented reverse function!
...
urlpatterns = [
path("universe/", include("src.universe.routings"), name="universe"),
path("moon/", test_app, name="moon"),
re_path(r"mars/(\d+)/$", test_app, name="mars"),
]
outer_router = URLRouter(urlpatterns)
...
app_name = "universe"
urlpatterns = [
re_path(r"book/(?P<book>[\w\-]+)/page/(?P<page>\d+)/$", test_app),
re_path(r"test/(\d+)/$", test_app),
path("/home/", test_app),
] Note: Django's routing system parses the And then, you can use original from django.urls import reverse as django_reverse
from channels.routing import reverse
...
django_reverse("mars", urlconf=root_urlconf, args=(5,)) # "/mars/5/"
reverse("mars", args=(5,)) # "/mars/5/"
django_reverse(
"universe:book",
urlconf=root_urlconf,
kwargs=dict(book="channels-guide", page=10),
) # "/universe/book/channels-guide/page/10/"
reverse("universe:book", kwargs=dict(book="channels-guide", page=10)) # "/universe/book/channels-guide/page/10/" This has been confirmed by |
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.
Getting closer!
Please see my comments and fix the lint warnings
channels/routing.py
Outdated
def reverse(*args, urlconf=None, **kwargs): | ||
"""reverse wrapper for django's reverse function | ||
|
||
Parameters | ||
---------- | ||
urlconf : str, optional | ||
The root path of the routings, by default None | ||
|
||
See the django's [reverse](https://docs.djangoproject.com/en/5.0/ref/urlresolvers/#reverse) | ||
for more details of the other arguments | ||
|
||
Returns | ||
------- | ||
str | ||
The reversed url | ||
""" | ||
if urlconf is None: | ||
urlconf = settings.ROOT_WEBSOCKET_URLCONF | ||
return django_reverse(*args, urlconf=urlconf, **kwargs) |
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.
I think this is a change that deserves its own discussion. I don't think we should introduce a new ROOT_WEBSOCKET_URLCONF
setting. Please revert the addition of this function.
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.
I want to discuss this.
When you don't set the ROOT_WEBSOCKET_URLCONF
, this reverse
function will be limited. Users must specify the urlconf
as the parent routings.py
every time.
- Why every time?
settings.ROOT_URLCONF
should be configured in the original django's routing system. And actually, django's reverse
function refers to ROOT_URLCONF
here
def resolve(path, urlconf=None):
if urlconf is None:
urlconf = get_urlconf()
return get_resolver(urlconf).resolve(path)
def reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None):
if urlconf is None:
urlconf = get_urlconf()
resolver = get_resolver(urlconf)
get_resolver
function is below;
def get_resolver(urlconf=None):
if urlconf is None:
urlconf = settings.ROOT_URLCONF
return _get_cached_resolver(urlconf)
This means original django's routing system parses all configured urlpatterns from ROOT_URLCONF
. That's why we can use reverse
function.
Example;
src
├── app1
│ ├── urls.py
│ └── routings.py
├── urls.py (root)
└── routings.py (root)
In settings.py
ROOT_URLCONF = 'src.urls' # this is entrypoint to parse all urls
That's why I imitated this implementation to use reverse
. If we want to use reverse
for channels, we should configure the "entrypoint" to parse all urls of channels.
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.
I totally see where you're coming from, but I don't want to mix in those new changes into this PR. I see this as 2 separate issues:
- Support nested urlpatterns
- Enable
reverse
This PR is (1). (2) requires more discussion and design, so I'd like to make that a separate PR. Do you mind splitting them up so we can discuss (2) in greater depth?
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.
OK!
Thanks! I left the comments. Please check them! And I'll fix linter errors from now. Also, I summarize that this PR's merit again. This will be helpful for the documentation.
more clear routing system in channels (goodbye nested URLRouter)When you configure src
├── app1
│ ├── urls.py
│ └── routings.py
├── urls.py (root)
└── routings.py (root) In ROOT_URLCONF = 'src.urls'
ROOT_WEBSOCKET_URLCONF = 'src.routings' In parent urlpatterns = [
path("app1/", include("src.app1.urls"), name="app1"),
path("home/", test_app, name="home"),
] In parent urlpatterns = [
path("app1/", include("src.app1.routings"), name="app1"),
path("home-ws/", test_app, name="home-ws"),
]
def get_websocket_application():
return URLRouter(urlpatterns) Almost same! In child app_name = 'app1'
urlpatterns = [
re_path(r"news/(\d+)/$", test_app, name="news"),
] In child app_name = 'app1'
urlpatterns = [
re_path(r"chats/(\d+)/$", test_app, name="chats"),
] Almost same! We don't need many And then we can Allow to use
|
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.
All I see left is:
- Removing
reverse
from this PR (submitting another PR is fine, I just don't want this PR to grow too much) - Fixing lints (I think that's just running
black
on the listed file)
Please check them again! |
I sorted modules. Memo: flake8 channels tests
black --check channels tests
isort --check-only --diff channels tests |
Just a simple question about the tests: what it the advantage of altering the Django unittests for routing does not use such pattern. IMO having files would be easier to read and will not encourage a potentially dangarous pattern (altering sys.modules). |
@sevdog I implemented the rollback function in |
Sorry for the delay, yeah I agree with @sevdog that we should use files instead for the tests here. Also, @carltongibson when you get a chance could you look over this PR and give your thoughts? |
@jjjkkkjjj I've begun looking at this. Can I ask you to add a draft at least for a release note and docs changes that would go with this please? (I see what you're proposing, but seeing docs clarify that. Thanks) (Also, I share the scepticism about the |
@carltongibson |
And then, should I use files instead of |
@jjjkkkjjj hold off on that last. If you could add the drafts for the docs (don't have to be perfect) I can think it through properly |
I'll work on adding the drafts by next week! |
@carltongibson @bigfootjon |
Thanks @jjjkkkjjj, that's great. Let me have a look 👀 |
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.
Hey @jjjkkkjjj.
I managed to get a look at it. 🤹 — I don't dislike it 🙂
I left some initial comments. Could you address, and then I take another look?
(Specifically, the autouse
on the fixture bothers me, as I can't see what's going on. If you can remove that, it'll be clearer.)
@@ -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 comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not keen on the autouse
. I can't see just from the code whether or when it's in play.
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.
This one is for rolling back the changes by sys.module
.
And then, should I use files instead of sys.modules?
@jjjkkkjjj hold off on that last. If you could add the drafts for the docs (don't have to be perfect) I can think it through properly
I ask you again.
Should I create the test files instead of using sys.module
?
channels/routing.py
Outdated
|
||
Parameters | ||
---------- | ||
child_url_pattern : URLResolver | |
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.
Why the trailing |
?
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.
Ah, I forgot to append to Any.
This function receives the url_pattern returned by path
, re_path
(Any) and include
(URLResolver)
@carltongibson |
@jjjkkkjjj Great thanks. Let me have another look. I will dig into those tests properly now. 👍 |
I re-created a new PR (#2037)
I tried to implement to support
URLRouter
withinclude
.This will be helpful project structure organized well.
Usage:
In parent's
routings.py
;In child's
routings.py
;Also, I've implemented the unittest for it in
test_url_router_nesting_by_include
intests/test_routing.py
.