Restrict urls on certain apps in Django - django

I've looked for an approach to add specific urls to a certain app. And restrict the urls for being used on other apps in Django.
I use Mezzanine and when a user goes to sub.domain.com he will see templates that are specific to that site. But when the user tries to go to the url on sub.domain.com/example he will see the url that is intended to be on domain.com/example. I want that to be a 404 for the user instead on the app.
Sorry for my bad english, hope you understand what I'm talking about.

You can get the current domain name (that the user is accessing) from request.META['HTTP_HOST']. Then based on that, raise 404 when the url is visited on certain domains.
from django.http import Http404
def my_restricted_view(request):
domain = request.META['HTTP_HOST']
if domain == 'sub.domain.com':
return render(request, "template_name", {})
else:
raise Http404

Related

Django user account delete and then return redirect and render

I want to allow a user to delete his account and upon deletion, I want the user to be logged out and see a html page account_deleted.html for confirmation.
students/views.py:
def delete_account(request):
user = User.objects.get(username=request.user)
user.delete()
context = {
"deleted_msg": "Account has been deleted",
}
return render(request, "students/account_deleted.html", context) and redirect("students:logout")
For logout, I'm using the built-in LogoutView function. The logout redirect URL in my settings.py is set to my home page.
students/urls.py:
path('logout/', LogoutView.as_view(), name='logout'),
In case of an account deletion, how can I make the delete_account(request) function return a render and redirect at the same time? Is that even possible?
Thanks!
You can log the user out before deleting the account by placing logout(request) before your user.delete() line. You will need to import it with from django.contrib.auth import logout.
As Bruno said in his answer, using a redirect instead of render is preferable after a POST request, and you should update your view to only respond to POST requests.
If you are having trouble with your website crashing after a user is deleted, make sure you are using the proper access control in all your views, eg by using the #login_required decorator or the equivalent mixin on all views that require a user to be logged in. If you do this the user will just be redirected to the login page if he or she is not logged in instead of crashing your site.
First things firsts: your view should 1/ only accept logged in users and 2/ only accept POST requests (you definitely dont want a GET request to delete anything from your database). Also, this:
User.objects.filter(username=request.user)
is useless - you already have the current user in request.user - and potentially dangerous if your auth backend allows for duplicated usernames.
and this:
return render(request, "students/account_deleted.html", context) and redirect("students:logout")
is of course plain wrong. A view returns one single HTTP response, you can't return two (it wouldn't make any sense), and you can't "and" two responses together (well, you can but the result is certainly not what you expect - read the FineManual about the and operator).
The proper solution is to 1/ manually log out the user (cf voodoo-burger's answer), 2/ use the messages framework to inform the user her accont has been deleted, and 3/ redirect to the home page (you ALWAYS want to redirect after a successful post, cf https://en.wikipedia.org/wiki/Post/Redirect/Get for the why).

Routing user profiles at the URL hierarchy root

Facebook, GitHub, and Twitter all place user profile URLs at the root of their URL hierarchies, e.g., http://twitter.com/jack.
This must be done so that other "system" URLs, like http://twitter.com/search are resolved first, so a user named #search can't hijack part of the site.
And if no system URL exists, and no such user profile is found, they must throw a 404.
What's the best way to achieve this using Django's URL routing? My current solution is:
urlpatterns = [
url(r'^admin/', admin.site.urls),
# etc, ..., then this last:
url(r'^(?P<username>.+)$', views.view_profile),
]
def view_profile(request, username):
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
raise Http404('User does not exist')
return HttpResponse(username + ' exists!')
In this way, the view_profile view is a catch-all for any URL that isn't handled elsewhere. However, it doesn't seem ideal that it is responsible for throwing a 404 exception if no user exists, I'd rather raise some "wrong route" signal that tells Django's URL router to resume attempting to route the request. This way the 404 is generated by Django the same way as if I did not have the catch-all route.
This question asks essentially the same thing, but the solutions involved creating a subpath for the user profile.
I would actually recommend a more RESTfull way of addressing the users profile. A nice organisation or a users profile rout would be /users/{pk}/ or if you want it easier for the client (we are doing it like that) /users/me/ so the client doesn't need to rememer it's id and the user is choosen from the session or auth/bearer token or whatever authorisation system you prefer.

django multiple domains, single app

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

Authenticate all urls except whitelist

Currently I wrote a method which authenticates a user using my company list of groups by getting the user using
request.meta['REMOTE_USER']
and comparing it to list of users in the group
My question is that how would I tie this in to my Django application such that it would check the users only for all urls except for a whitelist. I was thinking of calling this method in my views.py for the urls I needed but that seems to be maintenance nightmare
You could implement a middleware that would check a user against the url except the whitelist:
class MyAuthMiddleware(object):
def process_request(self, request):
if request.path in self.whitelist:
return
# Do the user checking otherwise

How do I redirect users who try to access admin area in Django?

I've noticed an interesting problem with Django's admin area. If I revoke my staff permissions and try to access /admin directly, I would normally expect a redirect to my login page with /admin/ in the query string as a future redirect. However, I get a proper page returned with HTTP code 200 which actually uses my admin/login.html template to render that requested page instead of redirecting. It seems the problem lies within the #staff_member_required decorator, which admin views obviously use.
The question is: is this done on purpose? If not, how can I change this behaviour without too much monkey-patching?
This is done on purpose, because many people implement redirects in thier sites which could block access to the admin panel. Because the admin panel is it's own app it redirects to itself.
# Put this code somewhere it will be imported REALLY early
from django.contrib.admin.views import decorators
def staff_member_required(view_func):
"""
Decorator for views that checks that the user is logged in and is a staff
member, displaying the login page if necessary.
"""
def _checklogin(request, *args, **kwargs):
if request.user.is_active and request.user.is_staff:
# The user is valid. Continue to the admin page.
return view_func(request, *args, **kwargs)
else:
return HTTPResponseRedirect('/my/login/page/')
return wraps(view_func)(_checklogin)
decorators.staff_member_required = staff_member_required #replaces the function in-place