When using i18n_patterns, how to reverse url without language code - django

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.

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 go from one page to another in django

I'm new to Django and python (infact my first language which I've only been learning for 3 months)
I'm creating this website using Django and I'm not able to go from one page to another using the href tag. it throws at me a 404 error saying "current path didn't match any of these"
This is my code
views.py
from django.shortcuts import render
from django.http import HttpResponseRedirect
from .models import off
# Create your views here.
def homepage(request):
return render(request=request,
template_name='main/home.html',
context={'toll' : off.objects.all})
def secondpage(request):
return render(request = request,
template_name = 'main/next.html')
main/urls.py
from django.urls import path
from . import views
app_name = 'main'
urlpatterns = [
path('',views.homepage,name='homepage'),
path('',views.secondpage,name='secondpage')
]
templates/mains/home.html
<div class="topnav">
Link
Link
Link
</div>
I also request the helper to simply it for me as I wouldn't be able to handle complex and advanced python terminology
Thanks In Advance From a Friend
Arvind
I think you should read this to start with Django.

Django: cannot import name 'url_patterns'

I have a weird edge-case where I need to use the data stored in urls.py within a view, but since the urls.py file imports the view, I am getting a circular import error when I try to import the url data into the view.
cannot import name 'url_patterns'
Does Django have any way of grabbing all the url patterns within a view that could circumvent this error? The fact that the reverse() function exists indicates that it might.
You can solve this typically (well it depends a bit), by importing the urls in your view(s) locally. Like:
# urls.py
import someapp.views
url_patterns = [
url('some_url/', someapp.views.some_view),
]
and then import the urls in the views like:
# views.py
def some_view(request):
from someapp.urls import url_patterns
# ...
# do something with urlpatterns
# return response
pass
We here thus postpone the importing of the urls.py in the views, until the view function is actually called. By that time both the views.py and urls.py are (usually) already loaded. You can however not call the view in the views.py directly, since then the import is done before the urls.py is loaded.
Note that usually you do not have to import url_patterns, and you can use reverse(..) etc. to obtain a URL that corresponds to a view function (even passing parameters into a dictionary, etc.). So I really advice to look for Django tooling to handle URLs.
Found an answer:
from django.urls import get_resolver
url_patterns = set(v[1] for k,v in get_resolver(None).reverse_dict.items())

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)
))

Following django form submission, redirect to page using newly created pk

I am trying to redirect the user to edit details of a task after task submission, but am having troubles redirecting to a page based on the newly created pk. Each view works without the return HttpResponseRedirect line. I have also tried arge=(instance.id) and kwargs=(instance.id) for the variable.
views.py
...
from django.http import HttpResponseRedirect, HttpResponseServerError, HttpResponseForbidden, Http404, HttpResponse
from django.core.urlresolvers import reverse
from django.shortcuts import render_to_response, get_object_or_404,render
...
def new_task(request):
...
...
task.save()
instance = task.save()
return HttpResponseRedirect(reverse('task_values', instance.id))
def task_values(request, task_id):
...
urls.py
from django.conf.urls.defaults import patterns, include, url
from django.http import HttpResponseRedirect
from django.views.generic.simple import direct_to_template
urlpatterns += patterns('core.views_entry',
#Task viewing/editing
(r'^task/(?P<task_id>\d+)/$','task_values'),
(r'^enter/$','new_task'),
return HttpResponseRedirect(reverse('task_values', kwargs={'task_id': instance.id}))
Also note that you don't need to save the task twice.
Edit OK, there's another problem. You haven't given your URLs specific names, which means that the only way to identify them is to pass the fully qualified view name:
reverse('core.views_entry.task_values', kwargs=...)
Or, better, use the the url() function to name your URL:
url(r'^task/(?P<task_id>\d+)/$','task_values', name='task_values'),
and you can use the original version I gave above.
Note that the error isn't telling you it's going to enter/<id>/, just that in that view it's trying to create the reverse URL for the redirection and failing.