My Django project "animals" has an app called "birds". In animals/urls.py the "birds" URIs are routed to birds/urls.py like this:
urlpatterns=[
url(r'^birds/', include('birds.urls')),
url(r'^b/', include('birds.urls')), # alias
]
The "birds/" is the official, permanent base URI; "b/" is accepted as a shortcut/alias.
How can I have the "b/" URIs (permanently) redirected to "birds/", such that even though users can enter "b/penguin", the address bar of the browser will (ultimately) show "birds/penguin"? I prefer not to touch any code in the "birds" app, because it should not know (care) how the project maps URIs to the app.
I have tried to use
RedirectView.as_view(pattern_name='birds'))
but this results in a 410 Gone response. And
RedirectView.as_view(url='/birds/'))
redirects /b/penguin to /birds/, killing my bird.
You should try something like that:
from django.views.generic import RedirectView
urlpatterns=[
url(r'^birds/', include('birds.urls')),
url(r'^b/(?P<path>.*)$', RedirectView.as_view(url='/birds/%(path)s')),
]
You can make a custom middleware to handle the redirects for you
class BirdMiddleware(MiddlewareMixin, object):
def process_view(self, request, view_func, view_args, view_kwargs):
if '/b/' in request.path:
return HttpResponseRedirect(request.path.replace('/b/', '/birds/'))
return None
The implementation may need a bit of work but the actual method stands, check the current path and if the /b/ is present, redirect it to your required destination.
Otherwise, you could specify a redirect view by iterating over every url in birds but this would get messy for urls in birds that are a namespace to other urls.
Related
I have experienced using reverse within get_absolute_url method in the model, but I wish I have an idea about the difference between reverse and redirect, I have tried to search on google about it but there is almost nothing
I don't know what should I write also to convince stack overflow that I don't have any other description
Reverse and redirect have a different meaning. Here is a simple explanation:
reverse in Django is used to find the URL of a given resource. Let's say that you have a blog website and from the main page, you want to provide links to your blog posts. You can of course just hard-code /posts/123/ and just change the ID of your blog post in URL, but that makes it hard to change your URL for the post in the future. That's why Django comes with reverse function. All you need to do is to pass the name of your URL path (defined in your urlpatterns) and Django will find for you the correct URL. It is called reverse because it is a reverse process of determining which view should be called for a given URL (which process is called resolving).
Redirects are not specific to Django or any other web frameworks. Redirect means that for a given URL (or action), the user should be instructed to visit a specific URL. This can be done by sending a special redirect request and from there the browser will handle it for the user, so no user action is required in that process. You can use reverse in redirect process to determine the URL that the user should be redirected to.
GwynBleidD has given you the answer, but there is a reason why you might be getting confused. The Django redirect shortcut accepts arguments in several different forms. One of them is a URLpattern mane, with arguments, that is then passed to reverse to generate the actual URL to redirect to. But that's just a shortcut, to enable a common pattern.
here's an example
app/views
#imports
def indexView(request):
....
return render(request, 'index.html', context)
def loginView(request):
....
return redirect('index')
def articleDetailView(request, id):
....
return redirect(reverse('article-comments', kwargs={'id':id})
def articleCommentsView(request, id):
....
return render(request, 'comment_list.html', context)
proj/urls
#imports
urlpatterns = [
....,
path('', include(app.urls))
]
app/urls
#imports
urlpatterns = [
....,
path('index/', index, name='index'),
path('login/', loginView, name='login'),
path('article/<int:id>/detail', articleDetailView, name='article-detail'),
path('article/<int:id>/comments/',articleCommentsView, name='article-comments')
....,
]
For loginView redirect will return url as-is i.e. 'index' which will be appended to base(project) urlpatterns. Here redirect(reverse('index')) will also work since kwargs is None by default for reverse function and 'index' view doesn't require any kwarg. It returns '/index/' which is passed to redirect(which again will be appended to base urls).
One thing to note is that reverse is used to make complete url - needed for redirect - that is shown in 'articleDetailview'.
The most basic difference between the two is :
Redirect Method will redirect you to a specific route in General.
Reverse Method will return the complete URL to that route as a String.
So I have this API URL on the back-end and I am wondering how to make a portion of it optional.
url(r'^api/documents/(?P<id>[0-9]+)$', GetDocumentsAPIView.as_view(), name='documents'),
So two things can happen coming from the front-end.
1) When the user logs in, it brings them to the /home which lists all of their projects. Clicking on the project brings them /documents/85 where the number is the number of the project and it lists all the documents for that project. This is sent to the API URL /api/documents/85. This much is working fine.
2) The other option is the user will just click on a Documents link in the navbar, which will just bring them to /documents. This should just go to the API URL /api/documents/ and eventually onto the serializer.py where their most recent project is determined and the documents for that are returned and rendered in the front-end
This I can't get working. Not sure if I can do it with just one url(), or if I need two. Was hoping the one I posted would look at the (?P<id>[0-9]+)$ as optional and if nothing was there would return the same GetDocumentsAPIView, but this is not the case.
EDIT: Including the view I am using for testing at this point. Just print() to see if anything is being routed to the view.
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
class GetDocumentsAPIView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, *args, **kwargs):
print(kwargs.get('id'))
return Response('Test')
No you need to add a URL pattern for r'^api/documents/$', which can launch the same view by the way. Only inside the view you'll have to assume id is optional:
if kwargs.get('id'):
# return specific document
else:
# return list of documents
I would rename your first URL pattern to 'view_document' so you can use the name 'documents' on the other, more generic view.
Note: I think your URL structure is wrong. Reading your URL, according to REST, I would expect to see the document with id 85 when fetching documents/85. If you actually are listing the docs of a project, the URL should be projects/85/documents/ or if unspecified projects/documents.
I have a requirement that whenever I login or attempt to request a view that has a login_decorator then the next page be a page where I am required to ask the user to select a business entity (irreespective of the original view requested).
Let's say that the page is http://127.0.0.1:8999/APP/business_unit
To achieve this I configured the following in my settings.py
LOGIN_REDIRECT_URL='/APP/business_unit_selector'
Now when i try to access http://127.0.0.1:8999/APP/range_list
the page goes to http://127.0.0.1:8999/APP/login?next=/APP/range_list I was expecting that the next page after login be /APP/business_unit
but instead, the next page was /APP/range_list
The browser address bar has http://127.0.0.1:8999/APP/login?next=/APP/range_list
Is it possible to achieve what I am trying in Django?
LOGIN_REDIRECT_URL is used unly when next is unspecified. In your test request there is next=/APP/range_list - and that address is used to redirect user after login.
Probably the easiest and most effective solution is to make your own decorator, similar to login_required which redirects to /APP/business_unit_selector&next=<redirect_url> if unit is not selected, and apply it together with login_required. It is not the most efficient solution in terms of redirects number, but is quite clean, and doesn't mess up the login page.
You will also have to handle next parameter in your business_unit_selector view, if you like to achieve natural flow.
Your decorator should be something like
from django.contrib.auth.decorators import login_required
from django.core.urlresolvers import reverse
from django.shortcuts import redirect
from django.utils.http import urlquote
import functools
def business_unit_required(view):
#login_required # probably you want to incorporate it here
#functools.wraps(view)
def wrapped(request, *args, **kwargs):
if not 'selected_business_unit' in request.session:
return redirect('%s?next=%s' % (
reverse('business_unit_selector'),
urlquote(request.get_full_path())))
return view(request, *args, **kwargs)
return wrapped
The reason that http://127.0.0.1:8999/APP/login?next=/APP/range_list is redirecting you to range_list after logging in, is because with next= you are overriding what is specified in your settings file, LOGIN_REDIRECT_URL='/APP/business_unit_selector'.
If I understand correctly you need to user to choose a business entity after logging in.
A couple solutions that come to mind are as follows:
1.) Don't use a separate forms for login and business entity. Instead combine them.
Username
Password
Business Entity
2.) You can also specify in your view if the user doesn't have a buisness entity ResponseRedirect("/APP/business_unit_selector")
docs here
I am trying to set my Django app work with multiple domains (while serving slightly different content)
I wrote this middleware:
class MultiSiteMiddleware(object):
def process_request(self, request):
host = request.get_host()
host_part = host.split(':')[0].split('.com')[0].split('.')
host = host_part[len(host_part)-1] + '.com'
site = Site.objects.get(domain=host)
settings.SITE_ID = site.id
settings.CURRENT_HOST = host
Site.objects.clear_cache()
return
In views I use this:
def get_site(request):
current_site = get_current_site(request)
return current_site.name
def view(request, pk):
site = get_site(request)
if site == 'site1':
# serve content1
...
elif site == 'site2'
# serve content2
...
But now there are 404 errors (I sometimes find them in logs, don't see them while browsing my site manually) where they aren't supposed to be, like my site sometimes is serving content for wrong domains, can they happen because of some flaw in the above middleware and view code or I should look somewhere else?
I had a similar requirement and decided not to use the django sites framework. My middleware looks like
class MultiSiteMiddleware(object):
def process_request(self, request):
try:
domain = request.get_host().split(":")[0]
request.site = Site.objects.get(domain=domain)
except Site.DoesNotExist:
return http.HttpResponseNotFound()
then my views have access to request.site
If you're seeing 404's for sites that aren't yours in your logs it would seem like somebody has pointed their domain at your servers IP address, you could use apache/nginx to filter these out before they hit your app, but your middleware should catch them (though possibly by raising an uncaught 500 error instead of a 404)
Serve multiple domain from one website (django1.8 python3+)
The goal is, obviously, to serve multiple domains, from one Django instance. That means, we use the same models, the same database, the same logic, the same views, the same templates, but to serve different things.
Searching the interwebs, I came to the idea of using the sites framework. The sites framework was designed to do exactly that thing. In fact, the sites framework has been used to do exactly that. But I haven't been able to know on which version of Django it was, and actually, I came to the idea the sites framework was just a vestigial module. Basically, it is just a table, with SITE_ID and SITE_URL, that you can easily access with a function. But I've been unable to find how, from that, you can do a multi-domain website.
So my idea has been, how to modify the url resolver. The idea behind all these is easy : www.domain1.com/bar is resolved to /domain1/bar, and www.domain2.foo is resolved to /domain2/foo. This solves the problem because, if you want to serve multiple domains, you just have to serve multiple folders.
In Django, to achieve this, you have to modify two things :
* the way Django routes requests
* the way django write urls
The way Django routes requests
Django routes requests with middlewares. That's it. So we just have to write a middleware that re-route requests.
To make it easier, middlewares can have a process_request method that process requests (WOW), before requests are handled. So let's write a DomainNameMiddleware
#!python
#app/middleware.py
class DomaineNameMiddleware:
"""
change the request path to add the domain_name at the first
"""
def process_request(self, request):
#first, we split the domain name, and take the part before the extension
request_domain = request.META['HTTP_HOST'].split('.')[-2]
request.path_info = "/%s/%s" % (request_domain, request.path.split('/')[1:])
The way Django writes URL
When I'm talking about django writing url, i'm principally thinking of the {% url %} template tag, the get_absolute_url methods, the resolve and resolve_lazy functions, and those basic Django thing. If we rewrite the way Django handle url, we have to tell Django to write url that way.
But basically it's pretty easy, thanks to Django.
You can easily rewrite basic Django function by just rewriting them, typically in init.py files of modules you added as apps. So :
#!python
#anyapp/__init__.py
from django.core import urlresolvers
old_reverse = urlresolvers.reverse
def new_reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None):
"""
return an url with the first folder as a domain name in .com
"""
TLD = 'com'
old_reverse_url = old_reverse(viewname, urlconf, args, kwargs, current_app)
# admin will add itself everytime you reload an admin page, so we have to delete it
if current_app == 'admin':
return '/%s' % old_reverse_url[len('admin'):].replace('adminadmin', 'admin')
return '//%s.%s/%s' % (app, TLD, path)
How to use it ?
I use it with base urls.py as dispatchedr.
#!python
#urls.py
from django.conf.urls import include, url
from domain1 import urls as domain1_urls
from domain2 import urls as domain2_urls
urlpatterns = [
url(r'^domain1/', include(domain1_urls, namespace='domain1')),
url(r'^domain2/', include(domain2_urls, namespace='domain2)),
]
Django's Sites framework has built-in middleware to accomplish this.
Simply enable the Sites framework and add this to your MIDDLEWARE:
'django.contrib.sites.middleware.CurrentSiteMiddleware'
This automatically passes a request object to Site.objects.get_current() on every request. It solves your problem by giving you access to request.site on every request.
For reference, the code as of 1.11 is:
from django.utils.deprecation import MiddlewareMixin
from .shortcuts import get_current_site
class CurrentSiteMiddleware(MiddlewareMixin):
"""
Middleware that sets `site` attribute to request object.
"""
def process_request(self, request):
request.site = get_current_site(request)
I will suggest you to use django-multisite .It will fulfill your requirement.
Try using the "sites" framework in django to get the domain name. You already know this I guess.
Take a look here: https://docs.djangoproject.com/en/1.7/ref/contrib/sites/#getting-the-current-domain-for-full-urls
See this:
>>> Site.objects.get_current().domain
'example.com'
Without the https://www or http://www.. Probably your domains will end in .org or some country .pe .ru etc not just .com.
There might be a case when people don't point to your domain but to your IP address for some reason, maybe development of testing so you should always raise an exception with Site.DoesNotExist
I have a web application which will return a user id based on the first segment of the url, much like Twitter:
http://www.myapplication.com/user-name-goes-here/
It can go deeper too, like so:
http://www.myapplication.com/user-name-goes-here/news/article_1/
In order to break the site down, I am using the following URL routing technique:
(r'^(?P<slug>\w+)/', include('myapp.sites.urls')),
This will then further route the user to the correct page, but as it stands I am having to query the database in every view in order to obtain the user_id based on the first url segment. I was hoping to somehow automate this so I don't have to bloat my views with the same code each time... my solution was to create some middleware which checks the url segment and returns a 404 if its not found:
from django.http import Http404
class DomainMiddleware(object):
def process_request(self, request):
from myapp.sites.models import Sites
dname = request.path.split('/')[1]
if not dname:
return
try:
d = Sites.objects.get(domain__exact=dname)
except Sites.DoesNotExist:
raise Http404
return
This works, but it's trying to parse EVERY request, even those to images, favicons etc.
My question is thus; Is there a way to run this query on every page load without clogging up my views with extra code? If middleware is the solution, how can I modify my code so that it doesn't include EVERY request, only those to successfully routed URLs?
Hope someone can help!
The Django server shouldn't be processing requests for static content URLs - certainly not in production anyway, where you'd have a different web server running to handle that, so this shouldn't be an issue there.
But if you say you'd like this to run for only sucessfully routed URLs, maybe you'd be better of using process_view rather than process_request in your middleware? http://docs.djangoproject.com/en/dev/topics/http/middleware/#process-view
process_view works at view level rather than request level, and provides a view_func argument which you can check so that your code doesn't run when it's the django.views.static.serve view used for serving static media during development.
Whatever happens you should defs be caching that database call if it's going to be used on every view.