How to specify environment variables in Apache site config for Django? - django

How do you specify environment variables for an Apache site?
I have a multi-tenant Django site being served by Apache+ModWSGI. Each site uses the same Django code, but their settings differ slightly. An environment variable tells Django which settings to load. Currently, I'm getting a separate wsgi_<site_name>.py file, containing the variable appropriate for each site, but this violate the DRY rule. I'd like to, instead, put this variable in the Apache site config, and use a single wsgi.py file for all sites.
I can't find much info on how to do this. I found this one old blog post suggesting I could use the Apache config syntax:
SetEnv SITE somename
along with a wsgi file like:
import os, sys
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
PROJECT_DIR = os.path.abspath(os.path.join(BASE_DIR, '..'))
sys.path.append(PROJECT_DIR)
os.environ['DJANGO_SETTINGS_MODULE'] = 'app.settings'
from django.core.wsgi import get_wsgi_application
_application = get_wsgi_application()
def application(environ, start_response):
os.environ['SITE'] = environ['SITE']
return _application(environ, start_response)
but this has no effect. If I print out the SITE variable, Django never receives the value "somename" and displays the default value.
As far as I can tell the mod_env component is still supported, and I've confirmed it's both installed and enabled on my server.

The problem was that I was calling get_wsgi_application() too early. The correct code is:
from django.core.wsgi import get_wsgi_application
def application(environ, start_response):
os.environ['SITE'] = environ['SITE']
_application = get_wsgi_application()
return _application(environ, start_response)

Related

Passing environment variables from apache via mod_wsgi to use in django 1.11 settings

Found a few versions of this question, such as Django get environment variables from apache, however the advice I've found so far doesn't seem to work with the latest LTS django (1.11).
I have an apache configuration which holds a number of environment variables, not limited to connection credentials for the DB. Im using this to make my code portable between dev/prod etc.
My apache conf just uses SetEnv to pass in some variables.
I've tried two different styles of approach to use these variables, both seem to suffer from the same issue; it needs to read the settings file before we can write to the environment, and the settings file requires values from the environment.
My two variants are;
import os
import django
from django.core.handlers.wsgi import WSGIHandler
from django.core.wsgi import get_wsgi_application
_application = get_wsgi_application()
def application(environ, start_response):
for key in [keys...]:
if environ.get(key):
os.environ[key] = environ.get(key)
return _application(environ, start_response)
and
import os
import django
from django.core.handlers.wsgi import WSGIHandler
class WSGIEnvironment(WSGIHandler):
def __call__(self, environ, start_response):
for key in [keys...]:
if environ.has_key(key):
print "Key: %s = %s" % (key,environ[key])
os.environ[key] = environ[key]
return super(WSGIEnvironment, self).__call__(environ, start_response)
os.environ.setdefault("DJANGO_SETTINGS_MODULE", 'apiClient.settings')
django.setup(set_prefix=False)
application = WSGIEnvironment()
Either way im trying to use the values in settings as;
KEY = "thing"
if os.environ.has_key("KEY"):
KEY = os.environ["KEY"]
You can't use SetEnv as the settings file is evaluated before the first request is handled. Setting environment variables from per request WSGI environ values was always a bad idea and can cause problems, so you shouldn't do that anyway.
Result is that you cannot set environment variables from the Apache configuration file. Set them from the WSGI script file. If they are things that should not be added to a Git repository, create a file on the specific host with the values in some format, and have the WSGI script file read that file from the local host to set them when first loaded and before any Django code is executed.
For Django > 1.7 get_wsgi_application calls django.setup() which initializes the settings. So if your env vars aren't set at that point you won't see them in the settings.
To get around it, don't actually call get_wsgi_application until you're ready. This works for me in wsgi.py:
def application(environ, start_response):
os.environ['APPLICATION_ENV'] = environ.get('APPLICATION_ENV', None)
return get_wsgi_application()(environ, start_response)
You have to defer initialising the Django app until the first request. Something like this in your wsgi.py:
from django.core.wsgi import get_wsgi_application
_application = None
def application(environ, start_response):
global _application
if _application == None:
for key in environ:
if key.startswith('ENV_'):
os.environ[key[4:]] = environ[key]
_application = get_wsgi_application()
return _application(environ, start_response)
It's a pity that there appears to be no option in mod_wsgi to set the initial environment when starting the daemon process, as you can with mod_fastcgi.

Redirecting legacy root URL in django

I'm trying to permanently redirect a legacy root URL to the new location in Django (hosted on Red Hat Openshift).
I've tried this solution but can't get it to work (even if the simplest case of http and without a further path). I'm not experienced with wsgi as you can probably guess and all help is very appreciated.
Here's my attempt to edit the last part of wsgi.py (redirecting from www.olddomain.com to www.newdomain.com). When I try to deploy it, trying to reach www.olddomain.com results in a error ("Can't reach this page"):
...
from django.core.wsgi import get_wsgi_application
_application = get_wsgi_application()
def application(environ, start_response):
if environ['HTTP_HOST'][:17] == 'www.olddomain.com':
start_response('301 Redirect', [('Location', 'http://www.newdomain.com/'),])
return []
return _application(environ, start_response)
Thank you for your help
Check your indentation level. Also, ensure that the value of the HTTP_HOST environment variable is the old domain.
from django.core.handlers.wsgi import WSGIHandler
_application = WSGIHandler()
def application(environ, start_response):
if environ['HTTP_HOST'][:21] != 'www.example.com':
start_response('301 Redirect', [
('Location', 'http://www.example.com/'),
])
return []
return _application(environ, start_response)

Set DJANGO_SETTINGS_MODULE through uwsgi

I am trying to find the best way to split my django settings, and have the different settings files served for dev and prod.
My wsgi file:
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
and in my uwsgi.ini file I have this:
env = DJANGO_SETTINGS_MODULE=project.settings.local
wsgi-file = project/wsgi.py
But the env from the uwsgi.ini file is not passed to wsgi. If I print os.environ in the wsgi file the DJANGO_SETTINGS_MODULE is not set.
Any ideas?
I like doing something like this in wsgi.py:
import os
import socket
socket_name = socket.gethostname()
if "stage" in socket_name:
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings.staging')
elif "prod" in socket_name:
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings.production')
else:
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings.local')
application = get_wsgi_application()
Your method of detecting whether you're on staging, prod, or local can be whatever you'd like, but the os.environ.setdefault command seems like it'll accomplish what you're hoping to do.
My solution was to also set the DJANGO_SETTINGS_MODULE in the bin/activate file. I have added it after export PATH like so:
export PATH
export DJANGO_SETTINGS_MODULE='project.settings.local'
You should find your bin folder in your project environment folder.

Set up the Django settings file via DJANGO_SETTINGS_MODULE environment variable with Apache WSGI

How to change the settings file used by Django when launched by Apache WSGI only with DJANGO_SETTINGS_MODULE environment variable ?
The Django documentation shows how-to achieve this with a different WSGI application file but if we don't want to create a dedicated WSGI file as well as a dedicated settings file for our different environments, using only environment variable DJANGO_SETTINGS_MODULE in Apache with SetEnv is not sufficent.
The variable is indeed passed to application call in environ variable but as the django.conf retrieve the settings like this :
settings_module = os.environ[ENVIRONMENT_VARIABLE]
it never see the right variable.
As only one instance of a Django application can be run within the context of a Python sub interpreter, the best way of doing things with mod_wsgi would be to dedicate each distinct Django site requiring a different settings, to a different mod_wsgi daemon process group.
In doing that, use a name for the mod_wsgi daemon process group which reflects the name of the settings file which should be used and then at global scope within the WSGI script file, set DJANGO_SETTINGS_MODULE based on the name of the mod_wsgi daemon process group.
The Apache configuration would therefore have something like:
WSGIDaemonProcess mysite.settings_1
WSGIDaemonProcess mysite.settings_2
WSGIScriptAlias /suburl process-group=mysite.settings_2 application-group=%{GLOBAL}
WSGIScriptAlias / process-group=mysite.settings_1 application-group=%{GLOBAL}
and the WSGI script file:
import os
try:
from mod_wsgi import process_group
except ImportError:
settings_module = 'mysite.settings'
else:
settings_module = process_group
os.environ['DJANGO_SETTINGS_MODULE'] = settings_module
To achieve that, you can use the following code in your application's wsgi.py file :
import os
from django.core.wsgi import get_wsgi_application
def application(environ, start_response):
_application = get_wsgi_application()
os.environ['DJANGO_SETTINGS_MODULE'] = environ.setdefault('DJANGO_SETTINGS_MODULE', 'myapp.settings')
return _application(environ, start_response)
Tip: don't forget to customize the myapp.settings string to match your default settings module.

Deploying Django project using mod_wsgi and virtualenv

Trying to deploy a Django 1.4 project using mod_wsgi and virtualenv, i'm running into a 500. The Apache error_log reveals:
mod_wsgi (pid=30452): Exception occurred processing WSGI script '/path/to/project/site-packages/projectapp/wsgi.py'.
[...] Traceback (most recent call last):
[...] File "/path/to/project/env/myenv/lib/python2.6/site-packages/django/core/handlers/wsgi.py", line 219, in __call__
[...] self.load_middleware()
[...] File "/path/to/project/env/myenv/lib/python2.6/site-packages/django/core/handlers/base.py", line 47, in load_middleware
[...] raise exceptions.ImproperlyConfigured('Error importing middleware %s: "%s"' % (mw_module, e))
[...] ImproperlyConfigured: Error importing middleware projectapp.content.middleware: "cannot import name SomeModel"
From the error message i would expect that this is some kind of a path issue. However, when the offending middleware is removed from the Django settings, the site launches just fine, and there are other modules loaded from projectapp.content, SomeModel is also loaded in this case, otherwise the whole site wouldn't be able to run.
The import error raised doesn't come directly from the middleware, as it doesn't import the model. SomeModel is defined in a speparate app which is actually checked out into the src directory of the virtualenv. But the directory containing this app is also in the path.
The wsgi.py file i'm using:
import os
import sys
sys.stdout = sys.stderr
sys.path.insert(0, '/path/to/project/env/myenv/lib/python2.6/site-packages/')
# The module inside the following directory
# defines SomeModel from the error message
sys.path.insert(0, '/path/to/project/env/myenv/src/some-app/')
sys.path.insert(0, '/path/to/project/site-packages/')
import django.core.handlers.wsgi
os.environ['DJANGO_SETTINGS_MODULE'] = 'projectapp.settings'
application = django.core.handlers.wsgi.WSGIHandler()
Printing sys.path after inserting the module paths shows everything in the expected order, /path/to/project/site-packages/ is listed first and /path/to/project/env/myenv/src/some-app/ (which defines SomeModel) second.
I've also tried a different variant, based on the example from the mod_wsgi docs:
import os
import sys
import site
ALLDIRS = [
'/path/to/project/site-packages/',
'/path/to/project/env/myenv/lib/python2.6/site-packages/',
'/path/to/project/env/myenv/src/some-app/',
]
# Remember original sys.path
prev_sys_path = list(sys.path)
sys.stdout = sys.stderr
# Add each new site-packages directory
for directory in ALLDIRS:
site.addsitedir(directory)
# Reorder sys.path so new directories are at the front
new_sys_path = []
for item in list(sys.path):
if item not in prev_sys_path:
new_sys_path.append(item)
sys.path.remove(item)
sys.path[:0] = new_sys_path
#activate_this = '/path/to/project/env/myenv/bin/activate_this.py'
#execfile(activate_this, dict(__file__=activate_this))
import django.core.handlers.wsgi
os.environ['DJANGO_SETTINGS_MODULE'] = 'projectapp.settings'
application = django.core.handlers.wsgi.WSGIHandler()
The error logged by Apache is exactly the same.
I have to add that the middleware is loaded just fine when i'm running the development server on the same machine, so i have no idea what's going wrong. Is there a way to get a better traceback from mod_wsgi?
EDIT:
Fogot to mention that i'm using mod_wsgi in daemon mode. Here are the relevant parts from my vhost config:
<VirtualHost x.x.x.x:80>
WSGIDaemonProcess foo user=foo threads=10 umask=0002
WSGIProcessGroup foo
WSGIScriptAlias / /path/to/project/site-packages/projectapp/wsgi.py
</VirtualHost>
Okay after hours of debugging this turned out to be a race condition caused by the middleware. The middleware i'm using is similiar to the FlatpageFallbackMiddleware from Django contrib and actually the import of the view caused the problem.
from projectapp.content.views import generic_content_detail
class ContentFallbackMiddleware(object):
def process_response(self, request, response):
[...]
Moving the import statement inside the process_response method solved the problem for me:
class ContentFallbackMiddleware(object):
def process_response(self, request, response):
from projectapp.content.views import generic_content_detail
[...]