Django hostname middleware gets cached - django

I created a Django project to manage two separate sites that share some backend code. Both of the sites are inside separate apps. Each app has its own models.py, views.py, templates etc...
To be able to react differently to different hostnames, I created an URLconf middleware:
class HostnameBasedUrlconfMiddleware(object):
"""This middleware parses the hostname from the request, and selects the
urlconf accordingly.
To set a custom urlconf according to the current hostname, add an URLCONF
dictionary to your settings.py file.
URLCONF = {
'example.com': 'urls_example',
'example.dev': 'urls_dev',
'admin.example.dev': 'apps.admin.urls'
}
If the hostname is not found in the URLCONF dictionary, the default
ROOT_URLCONF setting will be used.
"""
def process_request(self, request):
# Decide which urlconf to use. Fallback is to use the ROOT_URLCONF
# as defined in the settings.py file.
try:
hostname = request.META['HTTP_HOST']
request.urlconf = settings.URLCONF[hostname]
except (KeyError, AttributeError):
pass
return None
This seemed to work at first, but then I became aware that some kind of caching must be happening.
When starting the server and requesting site A, it would show up. If I then request site B, site A shows up. Sometimes (but not always), after several reloads, site B would finally show up. After restarting the server and requesting site B, it would show up, but now site A would show site B content.
This happened with the builtin devserver as well as with gunicorn.
I tried to request the site with curl to avoid browser caching, no difference.
I also suspected it could be some kind of template name collision, but all templates are inside a uniquely named subfolder inside their respective template folders.
I don't have memcached installed and I'm not using any caching middleware.
What could be the problem? Is there some internal automatic caching going on?

Here is the code in question that substitutes in the urlconf (for 1.3 at least):
django.core.handlers.base:
class BaseHandler(object):
[...snip...]
def get_response(self, request):
"Returns an HttpResponse object for the given HttpRequest"
from django.core import exceptions, urlresolvers
from django.conf import settings
try:
# Setup default url resolver for this thread, this code is outside
# the try/except so we don't get a spurious "unbound local
# variable" exception in the event an exception is raised before
# resolver is set
urlconf = settings.ROOT_URLCONF
urlresolvers.set_urlconf(urlconf)
resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
try:
response = None
# Apply request middleware
for middleware_method in self._request_middleware:
response = middleware_method(request)
if response:
break
if response is None:
if hasattr(request, "urlconf"):
# Reset url resolver with a custom urlconf.
urlconf = request.urlconf
urlresolvers.set_urlconf(urlconf)
resolver = urlresolvers.RegexURLResolver(r'^/', urlconf)
[...snip...]
So, it looks like it's just using the value directly from request.urlconf. And your middleware is setting the request value directly.
I'd install django-debug-toolbar to confirm whether or not the value for request.urlconf is a) being set or b) being changed along the way.
To make absolutely sure, why not change the code temporarily to something like:
request.urlconf = settings.URLCONF[hostname]
request.urlconf_set = datetime.datetime.now()
Then you can look at the values in the debug toolbar (or just output them in a template) to see what might be going on.
However, I would suggest instead of using middleware, that you simply set up different settings.py files for each domain. Then, in whatever web server you're using, set each one up to use its own .wsgi file, which points to its own settings file, like so:
settings_a.py:
from settings import *
ROOT_URLCONF = 'urls_a.py'
settings_b.py
from settings import *
ROOT_URLCONF = 'urls_b.py'

Related

How to fix cors error when loading static file served by Django runserver

Relevant info : Django version 2.2
I have installed django-cors-headers and added it to enabled apps and added the middleware to middleware settings as first middleware.
I have also configured the settings:
INSTALLED_APPS = [
...
"corsheaders",
"rest_framework",
"django.contrib.admin",
...
]
MIDDLEWARE = [
...
"corsheaders.middleware.CorsMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware",
...
]
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True
My setup has 2 projects running on different ports. Django server running on 8000 and my front-end running on 8080.
All API requests the front-end sends against back-end work just fine. But when I try to load one.js file served by the back-end project it fails with this error:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://127.0.0.1:8000/static/myscript.js. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing)
The way I load the script is by creating a new script element and add the URL http://127.0.0.1:8000/static/myscript.js as value for the src attribute on the script tag.
This error does not occur when I use a value like https://code.jquery.com/jquery-3.5.1.min.js instead of http://127.0.0.1:8000/static/myscript.js so it looks like there is some issue with static files being served by Django runserver, but what exactly and why is beyond me.
I know that there is a bunch of cors related issues like Django CORS on static asset, but they are not about my issue or have gone unanswered. Also, I noticed, that the requests going at static files bypass the cors middleware anyway, so perhaps I should be doing something else here instead?
Perhaps someone can help me out here?
Thanks
A slightly different approach based on Odif Yitsaeb's idea, however you don't need to remove staticfiles or mess with urlpatterns. Simply place the following code into your settings.py:
from django.contrib.staticfiles import handlers
# extend StaticFilesHandler to add "Access-Control-Allow-Origin" to every response
class CORSStaticFilesHandler(handlers.StaticFilesHandler):
def serve(self, request):
response = super().serve(request)
response['Access-Control-Allow-Origin'] = '*'
return response
# monkeypatch handlers to use our class instead of the original StaticFilesHandler
handlers.StaticFilesHandler = CORSStaticFilesHandler
Notes:
Don't use that in production (you shouldn't use devserver in production anyway)
The handler must be monkeypatched very early, before any requests are served. Placing the code in settings.py will do the trick
It looks like I have sort of found the answer:
What I did was I added INSTALLED_APPS.remove('django.contrib.staticfiles') into dev settings.
and I created view like
from django.contrib.staticfiles.views import serve
def cors_serve(request, path, insecure=False, **kwargs):
kwargs.pop('document_root')
response = serve(request, path, insecure=insecure, **kwargs)
response['Access-Control-Allow-Origin'] = '*'
return response
for serving static content. And I added it into urlconf like this:
from utils.views import cors_serve
...
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT, view=cors_serve)
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT, view=cors_serve)
This does take care of the issue for me. I know that the Django docs (https://docs.djangoproject.com/en/2.2/ref/contrib/staticfiles/#runserver) suggest that you can skip static files serving with using runserver like this: django-admin runserver --nostatic but that had absolutely no effect for me if I still had "django.contrib.staticfiles" in my INSTALLED_APPS

Django ALLOWED_HOSTS to accept local IPs through Apache

I'm serving a Django app with Apache.
In Django's settings.py I have DEBUG = False, therefore I had to allow some hosts, like: ALLOWED_HOSTS = ['.dyndns.org', 'localhost']. This works fine, however I would like to have the server accessible on the local network via its internal IP address as well, like: 192.168.0.x, or 127.0.0.1, etc. How could I define 192.* or 127.* in ALLOWED_HOSTS, if I'd like to avoid opening up the access entirely by ALLOWED_HOSTS = ['*']?
Following the recommendation from #rnevius, and based on the guidelines from #AlvaroAV in how to setup custom middleware in django, I've managed to solve with this middleware:
from django.http import HttpResponseForbidden
class FilterHostMiddleware(object):
def process_request(self, request):
allowed_hosts = ['127.0.0.1', 'localhost'] # specify complete host names here
host = request.META.get('HTTP_HOST')
if host[len(host)-10:] == 'dyndns.org': # if the host ends with dyndns.org then add to the allowed hosts
allowed_hosts.append(host)
elif host[:7] == '192.168': # if the host starts with 192.168 then add to the allowed hosts
allowed_hosts.append(host)
if host not in allowed_hosts:
raise HttpResponseForbidden
return None
and setting ALLOWED_HOSTS = ['*'] in settings.py no longer opens up for all hosts in an uncontrolled way.
Thanks guys! :)
For those wondering what this should be in Django 2.0.dev (In line with #Zorgmorduk's answer)
You need to make the object callable: django middleware docs
Create a folder named middleware in yourproject/yourapp/
Create an empty file __init__.py inside yourproject/yourapp/middleware folder.
Create another file, in this case filter_host_middleware.py
Add this code inside filter_host_middleware.py:
from django.http import HttpResponseForbidden
class FilterHostMiddleware(object):
def __init__(self, process_request):
self.process_request = process_request
def __call__(self, request):
response = self.process_request(request)
return response
def process_request(self, request):`
# use the same process_request definition as in #Zorgmorduk's answer
add yourapp.middleware.filter_host_middleware.FilterHostMiddleware to your MIDDLEWARE in yourproject's settings.py; additionally change ALLOWED_HOSTS=['*']
You are all set!

Dajaxice network error 404: method not found

I'm trying to create a basic submit form (like the dajaxice's example) but i keep receiving a 404 error from firebug console:
404 NOT FOUND 386ms
"NetworkError: 404 NOT FOUND - http://<my_website>/dajaxice/maynard.maynard_core.subscribe/"
My project folder structure is
/maynard/maynard_core/
This folder contains the main files of the project (views.py, ajax.py etc etc... main django project folder)
Inside the ajax.py file, there's the subscribe method:
from dajax.core import Dajax
from dajaxice.core import dajaxice_functions
from dajaxice.decorators import dajaxice_register
from views import subscribe_search
from forms import SubscriptionForm, SendMailForm
from django.core.mail import send_mail
def subscribe(request, form):
if request.POST:
dajax = Dajax()
form = SubscriptionForm(form)
try:
if form.is_valid():
url = form.cleaned_data['url_sub']
what = form.cleaned_data['what_sub']
where = form.cleaned_data['where_sub']
mail = form.cleaned_data['email']
subscribe_search(url,what,where,mail)
dajax.assign('#sub_mess_top','innerHTML','Thank you for subscribing to the search')
else:
dajax.add_css_class('#sub_mess_top','text error-message')
dajax.assign('#sub_mess_top','innerHTML','Couldn\'t complete the request, try again!')
return dajax.json()
except:
dajax.add_css_class('#sub_mess_top','text warning-message')
dajax.assign('#sub_mess_top','innerHTML','You already saved this search')
return dajax.json()
dajaxice_functions.register(subscribe)
Which is then called via this js method
function send_form_top(){
data = $('#subscribe').serializeObject(true);
Dajaxice.maynard.maynard_core.subscribe(Dajax.process,{'form':data});
}
The form is a basic form with action "#" and onclick="send_form_top();"
I followed the installation and configuration guide (settings configured, urls configured etc etc etc), and it's a very 101 implementation.
in urls.py
from dajaxice.core import dajaxice_autodiscover
dajaxice_autodiscover()
...
django.conf.urls.defaults.url(r'^%s/' % settings.DAJAXICE_MEDIA_PREFIX, django.conf.urls.defaults.include('dajaxice.urls')),
I added 'dajaxice' to my installed apps in settings.py, and DAJAXICE_MEDIA_PREFIX is 'dajaxice' (as in the docs).
Templates are fine too (since i have the dajaxice dynamically compiled js included)
But still i can't seem to be able to make it work. I checked throu the response headers, and this is what i get for the dajax.js file:
maynard_core: {
subscribe: function(callback_function, argv, custom_settings){
Dajaxice.call('maynard.maynard_core.subscribe', callback_function, argv, custom_settings);
},
...
This tells me that the submit method, which is in the ajax.py file inside maynard/maynard_core/ is actually included and the callback is correct too. I really don't know how to debug this any more, django logs shows nothing about it. Thanks all in advance, i'm really loosing my hair on this.
If you got a 404 error, definitely the problem is in your urls.py configuration.
There is any wildcard url above the dajaxice one? Try to put the dajaxice url conf on the beginning and see what happens.
Anyway... are your views.py, ajax.py, etc... inside any app? or all of them are in the root project folder. That could be the problem too.

Page not found message using Django

If a user type a random url(http://testurl/cdsdfsd) for a site ,how to issue page not found.I there any changes settings.py or how to handle this..
The django tutorial and docs have sections you should read.
You need to override the default 404 view.
in your urlconf:
handler404 = 'mysite.views.my_custom_404_view'
By default, django is rendering a 404.html template. Crete this file anywhere where your templates are found. E.g. you can create templates directory in your django project root, then add it to the TEMPLATE_DIRS in settings.py:
import os
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
TEMPLATE_DIRS = (
os.path.join(BASE_DIR, 'templates/'),
)
another solution is to write your own middleware which will check if there was 404 response, and the to decide what to display. This is especially useful if you want to have a fallback solution (like static pages) or to play with the response and e.g. perform a search on the site and show possible options.
here is an example middleware from django.contrib.flatpages. it check if url is defined in the database, if so - returns this page if not, pass and return default response.
class FlatpageFallbackMiddleware(object):
def process_response(self, request, response):
if response.status_code != 404:
return response # No need to check for a flatpage for non-404 responses.
try:
return flatpage(request, request.path_info)
# Return the original response if any errors happened. Because this
# is a middleware, we can't assume the errors will be caught elsewhere.
except Http404:
return response
except:
if settings.DEBUG:
raise
return response

In a django web application, how do you give users their own subdomain?

I'm starting a new web app project using Django and Pinax. I want to be able to give my users unique domain names like Wordpress and other sites do : username.wordpress.com. I'm not sure how to approach this with Django, since the url parsing logic (in urls.py) starts with the url AFTER the domain name.
More specifically, there will be multiple groups of users, each group having a unique name. Not sure that makes a difference, but I thought I should mention that.
Is there some way I can manipulate the http request so that the URL looks to Django as if the url were something like www.domain.com/groupname, but still showed in the browser address bar as groupname.domain.com?
You can use some custom middleware to intercept the request and get the subdomain from it. The following code will retrieve the subdomain and redirect to a view by reversing the named url.
Put it in a middleware.py file in your app.
Make sure you set up the middleware in your settings.py file.
Make sure you've named your view in urls.py
middleware.py
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
import re
subdomain_pattern = re.compile('(?P<subdomain>.*?)\..*?')
class SubdomainMiddleware(object):
def process_request(self, request):
match = subdomain_pattern.match(request.get_host())
subdomain = match.group('subdomain')
redirect_url = reverse('groups_detail', args=[subdomain])
return HttpResponseRedirect(redirect_url)
urls.py
from django.conf.urls.defaults import *
urlpatterns = patterns('',
url(r'^groups/(?P<name>.+)/$', 'groups.views.detail', {}, name='group_detail'),
)
Note: this code is untested.
Redirecting can alter the URL's appearance. If you want to avoid this, simply call the associated view, capture its result, and return it in an HttpResponse().
You need to handle this via your webserver. If you have Django urls like...
/users/<username>/
... then use rewrite rules in the webserver to map <username>.domain.com to domain.com/users/<username>/.
If you're using Apache, you can read up here. Otherwise, each webserver has their own conventions but all will support the notion of url rewrites.