I'd like to separate some apps on my website, and move some on differents domains.
But I'd like to avoid rewriting the urls in all the templates.
Is there an easy way to do this ?
The idea was to rewrite the reverse function (and thus, all the url dispatching thing).
Then, you have the first website called 'foo.com' and the second named 'bar.com'
This works for more sites than just two
app/urls.py
#!/usr/bin/python
# coding: utf-8
from django.conf.urls import include, url
from foo import urls as foo_urls
from bar import urls as bar_urls
url_patterns = [
url(r'^foo/',
include(foo_urls),
namespace='foo'),
url(r'^bar/',
include(bar_urls),
namespace='bar'),
]
app/__init__.py
#!/usr/bin/python
# coding: utf-8
"""
Just to simplify the multi website thing. Everything is served from one instance
"""
from __future__ import absolute_import
from django.core import urlresolvers
from django.http import Http404
from .settings LANGUAGES
old_reverse = urlresolvers.reverse
def remove_prefix(s, prefix):
return s[len(prefix):] if s.startswith(prefix) else s
def new_reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None):
""" Return an url with the domain if the page is situated on another domain
Note that this works for urls templatetags and for
django.core.urlresolvers.reverse, that are used a lot in get_absolute_url()
methods.
"""
old_reverse_url = old_reverse(viewname, urlconf, args, kwargs, current_app)
splitted_url = old_reverse_url.split('/')
if splitted_url[1] in [L[0] for L in LANGUAGES]:
(trash, lang, app, *path) = splitted_url
else:
(trash, app, *path) = splitted_url
lang = ''
if app == 'admin' or current_app == 'admin':
# fix a case where sometime the reverse has or has not a trailing /
old_reverse_url = remove_prefix(old_reverse_url, '/')
# fix a case where sometime the reverse has a double admin at the
# begining
old_reverse_url = old_reverse_url.replace('adminadmin', 'admin')
return '/%s' % old_reverse_url
path = '/'.join(path)
if lang == '':
return '//%s.com/%s' % (app, path)
else:
return '//%s.com/%s/%s' % (app, lang, path)
urlresolvers.reverse = new_reverse
templates examples
<a href="{% url 'foo:index' %}*>foo index</a>
Related
When i try go to http://127.0.0.1:8000/1395ec37e4/ i get error: Page not found at ...
I don't know why, twice, when i had changed variable getted_short_url to short_urlin urls.py and views.py(redirect_view) it could redirect me. I am confused...
Log from page:
Using the URLconf defined in cut_and_go.urls, Django tried these URL patterns, in this order:
<str:getted_short_url>
admin/
The current path, 1395ec37e4/, didn't match any of these.
views.py:
from hashlib import sha256
from .models import URL
from .forms import URLInput
from django.shortcuts import render, redirect
def input_form(request):
if request.method == 'POST':
form = URLInput(request.POST)
if form.is_valid():
getted_url = form.cleaned_data["full_url"]
transformed_url = 'https://' + getted_url
try:
URL.objects.get(full_url=transformed_url)
except URL.DoesNotExist:
maked_url = sha256(transformed_url.encode()).hexdigest()[:10]
URL(full_url=transformed_url, short_url=maked_url).save()
else:
form = URLInput()
return render(request, 'shortener/input_form.html', {'form': form})
def redirect_view(request, getted_short_url):
return redirect(URL.get_full_url(getted_short_url))
urls.py:
from . import views
from django.urls import path
urlpatterns = [
path('', views.input_form),
path('<str:getted_short_url>', views.redirect_view)
]
The str path converter does not catch the / character at the end of your URL. If you want to match only the part before the /, add a 2nd urlpattern (or replace the existing one if you don't care about URLs not ending with /) that includes the / at the end. If you want to catch this trailing slash in your path argument, use path instead of str. See Django docs for more information.
import os
from glob import glob
from importlib import import_module
from django.urls import re_path as _re_path, path as _path
def _glob_init(name):
name = name.replace('.', os.sep)
path = os.sep + '**'
modules = []
for module in glob(name + path, recursive=True):
importable = os.path.splitext(module)[0].replace(os.sep, '.')
if '__' in importable:
continue
try:
module = import_module(importable)
except ModuleNotFoundError:
module = import_module(importable[:-1])
modules.append(module)
return modules
class UrlManager:
def __init__(self, views_root):
self.views_root = views_root
self._url_patterns = []
def _path(self, route, kwargs=None, name=None, is_re=None):
func = _re_path if is_re else _path
def decorator(view):
_view = view # keep the original view
if isinstance(view, type):
view = view.as_view()
self._url_patterns.append(
func(route, view, kwargs=kwargs, name=name or view.__name__)
)
return _view
return decorator
def path(self, route, kwargs=None, name=None):
return self._path(route, kwargs=kwargs, name=name, is_re=False)
def re_path(self, route, kwargs=None, name=None):
return self._path(route, kwargs=kwargs, name=name, is_re=True)
#property
def url_patterns(self):
if isinstance(self.views_root, str):
_glob_init(self.views_root)
else:
for root in self.views_root:
_glob_init(root)
return self._url_patterns
Basic usage:
# app.urls.py
app_urls = UrlManager('app.views')
# app.views.py
from models import SomeModel
from urls import app_urls
#app_urls.path('foo/', name='foo')
def view(request):
return response
# project.urls.py
from django.urls import include
from app.urls import app_urls
urlpatterns = [
path('', include(app_urls.urlpatterns))
]
Okay, now:
Everything works when you already have the migrations up and running, but when you want to create a new migration; due the fact that this code flow is something like:
--project urls.py imports app.urls
--app.urls imports views
--views imports models
--models doesn't exist yet because this is a migration command run
--error
I need to somehow either create a module named the instance so I can use include('module.urls') or some other way to postpone the imports.
Repo is at https://github.com/isik-kaplan/django_urls if anyone wants to open a pr.
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)
))
I am trying to make a website available in different languages. I am following some tutorial I found on GitHub (https://github.com/mjp/django-multi-language).
views.py
#! -*- coding: utf-8 -*-
from django.shortcuts import render
from django.utils.translation import ugettext as _
# ──────────────────────────────────────────────────────────────────────────────
def index(request):
# Rendering the page
context = { 'foobar':_('Hello !') }
response = render(request, 'home/index.html', context)
request.session['django_language'] = 'fr'
return response
index.html
{% load i18n %}
<p>{{ foobar }}</p>
settings.py
# Project path for further access
PROJECT_PATH = path.realpath(path.dirname(__file__))
# Locale paths
LOCALE_PATHS = ( # Next to the settings.py
PROJECT_PATH+'/locale/', # the time I find a better way to
) # avoid hard coded absolute path
Localization is quite straightforward, but my page keeps saying Hello ! instead of Bonjour !.
Edit
- documentation/
- locale/ <-- I want it here
- sources/
- application1/
- application2/
- projectname/
- settings.py
- ...
- locale/ <-- Currently here
- toolbox/
Setting request.session['django_langauge'] is not helpfull in this case. The LocaleMiddleware which I assume you have installed will parse that before the request gets to the view function. You should directly use the translation functions to change language for this.
from django.utils.translation import activate
def index(request):
activate('fr')
....
return response
Is there a way I can apply the login_required decorator to an entire app? When I say "app" I mean it in the django sense, which is to say a set of urls and views, not an entire project.
Yes, you should use middleware.
Try to look through solutions which have some differences:
http://www.djangosnippets.org/snippets/1179/ - with list of exceptions.
http://www.djangosnippets.org/snippets/1158/ - with list of exceptions.
http://www.djangosnippets.org/snippets/966/ - conversely with list of login required urls.
http://www.djangosnippets.org/snippets/136/ - simplest.
As of Django 3+, you can set login_require() to an entire app by applying a middleware. Do like followings:
Step 1: Create a new file anything.py in your yourapp directory and write the following:
import re
from django.conf import settings
from django.contrib.auth.decorators import login_required
//for registering a class as middleware you at least __init__() and __call__()
//for this case we additionally need process_view() which will be automatically called by Django before rendering a view/template
class ClassName(object):
//need for one time initialization, here response is a function which will be called to get response from view/template
def __init__(self, response):
self.get_response = response
self.required = tuple(re.compile(url) for url in settings.AUTH_URLS)
self.exceptions = tuple(re.compile(url)for url in settings.NO_AUTH_URLS)
def __call__(self, request):
//any code written here will be called before requesting response
response = self.get_response(request)
//any code written here will be called after response
return response
//this is called before requesting response
def process_view(self, request, view_func, view_args, view_kwargs):
//if authenticated return no exception
if request.user.is_authenticated:
return None
//return login_required()
for url in self.required:
if url.match(request.path):
return login_required(view_func)(request, *view_args, **view_kwargs)
//default case, no exception
return None
Step 2: Add this anything.py to Middleware[] in project/settings.py like followings
MIDDLEWARE = [
// your previous middleware
'yourapp.anything.ClassName',
]
Step 3: Also add the following snippet into project/settings.py
AUTH_URLS = (
//disallowing app url, use the url/path that you added on mysite/urls.py (not myapp/urls.py) to include as your app urls
r'/your_app_url(.*)$',
)
I think you are looking for this snippet, containing login-required middleware.
This is an old question. But here goes:
Django Decorator Include
This is a substitute of include in URLConf. Pefect for applying login_required to an entire app.
I clicked all the links in the anwsers, but they were all based on some kind of regular expressions. On Django 3+ you can do the following to restrict for a specific app:
Declare app_name="myapp" in your app's urls.py (https://docs.djangoproject.com/en/3.2/intro/tutorial03/#namespacing-url-names)
(now all these urls should be called with there namespace "myapp:urlname")
Create a middleware.py file in your app with this:
from django.contrib.auth.views import redirect_to_login
from django.core.exceptions import ImproperlyConfigured
from django.urls import resolve
class LoginRequiredAccess:
"""All urls starting with the given prefix require the user to be logged in"""
APP_NAME = 'myapp'
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if not hasattr(request, 'user'):
raise ImproperlyConfigured(
"Requires the django's authentication middleware"
" to be installed.")
user = request.user
if resolve(request.path).app_name == self.APP_NAME: # match app_name defined in myapp.urls.py
if not user.is_authenticated:
path = request.get_full_path()
return redirect_to_login(path)
return self.get_response(request)
Put "myapp.middleware.LoginRequiredAccess" in your MIDDLEWARE constant from settings.py
Then in your main project urls.py
urlpatterns = [
path('foobar', include('otherapp.urls')), # this will not be redirected
path('whatever', include('myapp.urls')), # all these urls will be redirected to login
]
On of the avantage of this method is it can still works with a root url path, e.g path('', include('myapp.urls')), while the others will do an infinite redirect loop.
I'm wondering if there is any solution to make it works like this:
/app/app.py
class AppConfig(AppConfig):
login_required = True
/project/urls.py
urlpatterns = [
url(r'app/', include('app.urls', namespace='app'))
]
/common/middleare.py
def LogMiddleware(get_response):
def middleware(request):
# solution 1
app = get_app(request)
if app.login_required is True and request.is_authenticated is Fasle:
return HttpResponseRedirect(redirect_url)
# solution 2
url_space = get_url_space(request.get_raw_uri())
if url_space.namespace in ['app', 'admin', 'staff', 'manage'] and \
request.is_authenticated is False:
return HttpResponseRedirect(redirect_url)
I will check if there is any methoded to get the app or url name of a request. I think it looks prettier.