Utilizing Django url resolver vs. re-inventing the request.path parsing wheel - django

Let's say I had the following array:
sub_urls = [
'/',
'/<uuid:id>',
'/test',
'/test/<uuid:id>'
]
The URL stings are very similar to what you'd find in Django's urlpatterns
my question:
can django.url.resolve be used to find the pattern in the sub_urls array given a path string like /test/189e8140-e587-4d5d-ac5c-517fd55c67bc without me having to re-invent the wheel here?
PLEASES NOTE: this isn't a question about how I route from urls to views in Django. Please don't tell me how to "solve it with urlpatterns, re_path etc." This is about utilizing this same mechanism by which django matches up a URL with a view --- for a COMPLETELY different purpose. I can write this myself, but I wanted to know if there's a way to re-use what Django has as it CLEARLY has the same needs when parsing request.path into the correct view.

Since django.urls.resolve calls get_resolver which creates a URLResolver (through _get_cached_resolver), you can instead create a URLResolver yourself:
# Define an empty view
def empty_view(*args, **kwargs):
return None
# Define the urlpatterns
from django.urls import path
sub_urls = [
'/',
'/<uuid:id>',
'/test',
'/test/<uuid:id>'
]
urlpatterns = [path(u, empty_view) for u in sub_urls]
# Create a resolver
from django.urls.resolvers import URLResolver, RegexPattern
resolver = URLResolver(RegexPattern(r'^'), urlpatterns)
# Match a path
# Returns a django.urls.resolvers.ResolverMatch object
result = resolver.resolve('/test/189e8140-e587-4d5d-ac5c-517fd55c67bc')
print(result)
print(result.route)
The above code will output:
ResolverMatch(func=__main__.empty_view, args=(), kwargs={'id': UUID('189e8140-e587-4d5d-ac5c-517fd55c67bc')}, url_name=None, app_names=[], namespaces=[], route=/test/<uuid:id>)
/test/<uuid:id>
If you're set on using django.urls.resolve, you could move urlpatterns to a module, let's say my_urls:
# Define an empty view
def empty_view(*args, **kwargs):
return None
# Define the urlpatterns
from django.urls import path
sub_urls = [
'',
'<uuid:id>',
'test',
'test/<uuid:id>'
]
urlpatterns = [path(u, empty_view) for u in sub_urls]
Notice we had to remove the / suffixes from the URLs above because _get_cached_resolver creates a RegexPattern that starts with /.
And then pass the module name as the second argument to resolve:
# Configure settings (only needed when this is not being run in an existing Django app)
from django.conf import settings
settings.configure()
# Match a path
# Returns a django.urls.resolvers.ResolverMatch object
from django.urls import resolve
result = resolve('/test/189e8140-e587-4d5d-ac5c-517fd55c67bc', urlconf='my_urls')
print(result)
print(result.route)
I don't believe the caching of _get_cached_resolver should be an issue as #Clepsyd mentioned since functools.lru_cache caches depending on the arguments it receives.

Related

Django Save Path into Variable

I have a django path that passes the URL I need, but I want to store it into a variable which I can use in a mailing API the path is:
path('activate/(<uidb64>[0-9A-Za-z_\-]+)/(<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})', views.activate, name='activate'),
"uid": urlsafe_base64_encode(force_bytes(account.pk)),
'token': password_reset_token.make_token(account),
I want the string to have a value similar to this: http://127.0.0.1:8000/auth/activate/(NDM%5B0-9A-Za-z_%5C-%5D+)/(as9osn-a59ae3d7196bb1fa693e770fb87f19c1%5B0-9A-Za-z%5D%7B1,13%7D-%5B0-9A-Za-z%5D%7B1,20%7D)
I am getting this: http://127.0.0.1:8000/auth/activate/NTQ/asbda1-165d68dbe6fee8c47f5099c4ab709c48
You are using regex-syntax instead of path syntax. You thus should use the re_path(…) function [Django-doc] to specify a regex, or convert it to a path syntax. We thus can implement this with:
from django.urls import re_path
urlpatterns = [
# &vellip;,
re_path('activate/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', views.activate, name='activate'),
# &vellip;
]

Check(conditional) if the current url is the same with a url declared in path

In the main urls.py I have:
urlpatterns = [
path('items/', include('items.urls', namespace='items')),
....
]
In items urls.py I have:
urlpatterns = [
path('item/add/', ItemCreateView.as_view(), name='create_item'),
]
I want to check in a view/dispatch() if the current page url is the same with the one in the path, something like:
if self.request.path == 'items:create_items'
You can use reverse to convert the namespaced pattern name items:create to the URL.
from django.urls import reverse
if self.request.path == reverse('items:create_items'):
Depending on your server setup and whether you are concerned about the querystring, you may want to use request.path_info or request.get_full_path() instead of request.path.

Changing a old Django URL to the new paths

So I am making a new site in Django 2.0 and was following this tutorial on making a user registration form with an activation email and my understanding of the new Django 2 is not good enough so was asking what would be the Django 2 equivalent of this URL
url(r'^activate/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', views.activate, name='activate'),
There is no straight conversion for your path you could either use a converter as stated in the docs to convert the token. Here the example from the docs:
class FourDigitYearConverter:
regex = '[0-9]{4}'
def to_python(self, value):
return int(value)
def to_url(self, value):
return '%04d' % value
register the converter
from django.urls import path, register_converter
from . import converters, views
register_converter(converters.FourDigitYearConverter, 'yyyy')
urlpatterns = [
path('articles/2003/', views.special_case_2003),
path('articles/<yyyy:year>/', views.year_archive),
...
]
or you can just regex the path like you currently are:
from django.urls import path, re_path
from . import views
urlpatterns = [
path('articles/2003/', views.special_case_2003),
re_path(r'^activate/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', views.activate, name='activate')
]
I would just stick to the regex using re_path since you know it works and its already done.
Here is the link to the docs:
https://docs.djangoproject.com/en/2.0/topics/http/urls/

When using i18n_patterns, how to reverse url without language code

I am using i18n_patterns but I want to use reverse to create a link to the page without language in the url (such that the user will be redirected based on cookies and headers and such).
I have tried
from django.utils.translation import activate, deactivate, get_language
current_lang = get_language()
deactivate()
url = reverse(things)
activate(current_lang)
That works for getting other language versions using activate(target_lang), but if I deactivate I just get urls for the default language (/en/account/ but I want /account/).
I already thought getting alternate language versions is overly complicated, but this I cannot manage at all. Any hints? (Without manually stripping LANGUAGE_CODE from the url)
UPDATE: I also tried
from django.core.urlresolvers import get_resolver
get_resolver(None).reverse(*args, **kwargs)
but get NoReverseMatch
I think the easiest way is to let Django resolve the URL with the language prefix and then just remove the language prefix.
You can write the following function:
import re
from django.core.urlresolvers import reverse
def reverse_no_i18n(viewname, *args, **kwargs):
result = reverse(viewname, *args, **kwargs)
m = re.match(r'(/[^/]*)(/.*$)', result)
return m.groups()[1]
Now, anywhere in your code you can do something like this:
from myproject.utils import reverse_no_i18n
def my_view(request):
return HttpResponseRedirect(reverse_no_i18n('my_view_name'))
You might also want to create a custom {% url %} templatetag which calls your custom function.
I also spent time to find a nice solution and here is mine.
Next to main urls file ('my_project/urls.py'), create the file 'my_project/urls_without_lang.py' with the content below.
Then, you can use reverse('viewname', urlconf='my_project.urls_without_lang')
Django=<1.11
from copy import copy
from django.urls.resolvers import LocaleRegexURLResolver
from .urls import urlpatterns as urlpatterns_i18n
"""
Purpose of this file is to be able to reverse URL patterns without language prefix.
This is usefull to build URL meant to be communicated "outside" of the domain without any language duty.
To use it with 'reverse' method (from django.shortcuts module), simply give the additional parameter:
`urlconf='my_project.urls_without_lang'`
Example: `reverse('viewname', urlconf='my_project.urls_without_lang')`
"""
urlpatterns = copy(urlpatterns_i18n)
for el in urlpatterns_i18n:
if isinstance(el, LocaleRegexURLResolver):
urlpatterns.remove(el)
urlpatterns += el.url_patterns
Django>1.11
from copy import copy
from django.urls import URLResolver
from .urls import urlpatterns as urlpatterns_i18n
urlpatterns = copy(urlpatterns_i18n)
for el in urlpatterns_i18n:
if isinstance(el, URLResolver) and isinstance(el.urlconf_name, list):
urlpatterns.remove(el)
urlpatterns += el.url_patterns
Hope that will help some of you.

Recursive URL routing in Django

I want to have a (fairly simple) emulation of SELECT queries through URLs.
For example, in a blogging engine, You'd like /tag/sometag/ to refer to the posts having the sometag tag. Also /tag/sometag/or/tag/other/and/year/2013 should be a valid URL, beside other more complex urls. So, having (theoretically) no limit on the size of the url, I would suggest this should be done recursively, but how could it be handled in Django URL Routing model?
I would use a common URL pattern for all those URLs.
url(r'^query/([\w/]*)/$', 'app.views.view_with_query'),
You would receive all the "tag/sometag/or/tag/other/and/year/2013" as a param for the view.
Then, you can parse the param and extract the info (tag, value, tag, value, year, value) to make the query.
update 2021
django.conf.urls.url is deprecated in version 3.1 and above. Here is the solution for recursive url routing in django:
urls.py
from django.urls import path, re_path
from .views import index
urlpatterns = [
re_path(r'^query/([\w/]*)/$', index, name='index'),
]
views.py
from django.shortcuts import render, HttpResponse
# Create your views here.
def index(request, *args, **kwargs):
print('-----')
print(args)
print(kwargs)
return HttpResponse('<h1>hello world</h1>')
If I call python manage.py run server, and go to 'http://127.0.0.1:8000/query/nice/', I can see these in terminal:
-----
('nice',)
{}
I know this is an old question but for those who reach here in future, starting from django 2.0+, you can use path converter with path to match the path in the url:
# urls.py
urlpatterns = [
path('query/<path:p>/', views.query, name='query'),
]
# views.py
def query(request, p):
# here, p = "tag/sometag/or/tag/other/and/year/2013"
...