Recursive URL routing in Django - 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"
...

Related

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

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.

How to make dynamic URLs with Django

my new project needs to fetch URLs from database. Example:
Backend add Categorys and URLs should work like:
www.example.com/hardware
if I make in Backend a new subcategory:
www.example.com/hardware/notebooks
If I add a article:
ww.example.com/hardware/notebooks/lenovo-e410 or articlenumbers.
But I didnt find out where I can add urls from SQL Queries.
But I didnt find out where I can add urls from SQL Queries.
You don't need to; You should create fixed patterns after your dynamic urls.
So in your urls.py:
from django.urls import path
from . import views
urlpatterns = [
path('/<str:category>/', views.hardware),
path('/<str:category>/<str:subcategory>/', views.subcategory),
path('/<str:category>/<str:subcategory>/<str:article>/', views.areticle),
]
or if you prefer to just have urls for your arricles, remove the first two patterns.
Then in the corresponding views for each pattern, you should look up your database for the urls.
I think you haven't gone through Django tutorial, I request you to go through the tutorial given at https://docs.djangoproject.com/en/3.0/intro/tutorial01/#write-your-first-view
Add the following pattern to urls.py
# urls.py
from django.urls import path
urlpatterns = [
path('/<str:category>/<str:product>/'),
]
# views.py
def product_page_view(request, category, product):
# ... your stuffs
Read about Django URLs at https://docs.djangoproject.com/en/3.0/topics/http/urls/

Generate Django sitemap while using site framework

I am using sites framework with RequestSite (no SITE_ID set) to generate content based on domain. I need to generate sitemaps for each domain with different results but I didnt find a way how to make this two frameworks work together. Is there any way to get Site of the current request in Sitemap? (getting it from SITE_ID config is not an option for me).
Here is an example of what I would like to do:
from django.contrib.sitemaps import Sitemap
from blog.models import Entry
class BlogSitemap(Sitemap):
def items(self, request):
return Entry.objects.filter(is_draft=False, site=request.site)
But its not possible because there is no request in items(). Is there any other way how to filter items in sitemap based on site?
Try following example:
from django.contrib.sitemaps import Sitemap
from django.contrib.sitemaps.views import sitemap
from blog.models import Entry
class BlogSitemap(Sitemap):
_cached_site = None
def items(self):
return Entry.objects.filter(is_draft=False, site=self._cached_site)
def get_urls(self, page=1, site=None, protocol=None):
self._cached_site = site
return super(BlogSitemap, self).get_urls(page=page, site=site, protocol=protocol)
And in urls.py
urlpatterns = [
url('sitemap.xml', sitemap, {
'sitemaps': {'blog': BlogSitemap}
}, name='django.contrib.sitemaps.views.sitemap'),
# ...
# other your urls
]
This should work now. Let me know if you'll have any questions.

Django Caching on front-end

I was working with 2 applications that are within a DJango project: "customer" and "vendors". Each application has a HTML file named "testindex.html".
Whenever I typed:
http://myhost/customer/basic_info
the correct page would show up
If I typed
http://myhost/vendors/basic_info
the page from http://myhost/customer/basic_info would show up
I found out that it was due to caching (since both applications use "testindex.html"). So again, "testindex.html" is caching.
How can one get around this problem?
TIA
Details are listed below. I have the following views defined:
urls.py for the project
urlpatterns = [
... snip ...
url(r'^customer/', include('libmstr.customer.urls')),
url(r'^vendors/', include('libmstr.vendors.urls')),
]
views.py for customer
from django.shortcuts import render
def basic_info(request):
return render(request, 'testindex.html', {})
views.py for vendors
from django.shortcuts import render
def basic_info(request):
return render(request, 'testindex.html', {})
urls.py for customers
from django.conf.urls import url
from . import views
# list of templates
app_name = 'customer'
urlpatterns = [
url(r'^basic_info/$', views.basic_info, name='basic_info'),
]
urls.py for vendors
from django.conf.urls import url
from . import views
# list of templates
app_name = 'vendors'
urlpatterns = [
url(r'^basic_info/$', views.basic_info, name='basic_info'),
]
It sounds like you have two templates, customers/templates/testindex.html and vendors/templates/testindex.html.
When you call render(request, 'testindex.html', {}), the app directories template loader searches the templates directory for each app in INSTALLED_APPS, and stops the first time it finds a match. If customers is above vendors in INSTALLED_APPS, then it will always use the customers template.
For this reason, Django recommends that you name your templates customers/templates/customers/testindex.html and vendors/templates/vendors/testindex.html, and change your views to use customers/testindex.html and vendors/testindex.html. This way you avoid clashes.

Best way to handle legacy urls in django

I am working on a big news publishing platform. Basically rebuilding everything from ground zero with django. Now as we are almost ready for the launch I need to handle legacy url redirects. What is the best way to do it having in mind that I have to deal with tenths of thousands of legacy urls?
Logic should work like this: If none of existing urls/views where matched run that url thorough legacy Redirect urls patterns/views to see if it can provide some redirect to the new url before returning 404 error.
How do I do that?
You may want to create a fallback view that will try to handle any url not handled by your patterns. I see two options.
Just create a "default" pattern. It's important to this pattern to
be the last within your urlpatterns!
in your urls.py:
urlpatterns = patterns(
'',
# all your patterns
url(r'^.+/', 'app.views.fallback')
)
in your views.py:
from django.http.response import HttpResponseRedirect, Http404
def fallback(request):
if is_legacy(request.path):
return HttpResponseRedirect(convert(request.path))
raise Http404
Create a custom http 404 handler.
in your urls.py:
handler404 = 'app.views.fallback'
in your views.py
from django.http.response import HttpResponseRedirect
from django.views.defaults import page_not_found
def fallback(request):
if is_legacy(request.path):
return HttpResponseRedirect(convert(request.path))
return page_not_found(request)
it may seem to be a nicer solution but it will only work if you set DEBUG setting to False and provide custom 404 template.
Awesome, achieved that by using custom middleware:
from django.http import Http404
from legacy.urls import urlpatterns
class LegacyURLsMiddleware(object):
def process_response(self, request, response):
if response.status_code != 404:
return response
for resolver in urlpatterns:
try:
match = resolver.resolve(request.path[1:])
if match:
return match.func(request, *match.args, **match.kwargs)
except Http404:
pass
return response
Simply add this middleware as a last middleware in MIDDLEWARE_CLASSES list. Then use urls.py file in your legacy app to declare legacy urls and views which will handle permanent redirects. DO NOT include your legacy urls in to main urls structure. This middleware does it for you, but in a bit different way.
Use the Jacobian's django-multiurl. There is a django ticket to address the issue someday, but for now django-multiurl works very good.
Before:
# urls.py
urlpatterns = patterns('',
url('/app/(\w+)/$', app.views.people),
url('/app/(\w+)/$', app.views.place), # <-- Never matches
)
After:
# urls.py
from multiurl import multiurl, ContinueResolving
from django.http import Http404
urlpatterns = patterns('', multiurl(
url('/app/(\w+)/$', app.views.people), # <-- Tried 1st
url('/app/(\w+)/$', app.views.place), # <-- Tried 2nd (only if 1st raised Http404)
catch=(Http404, ContinueResolving)
))