Declare Django settings to use with mod_wsgi/apache - django

I have multiple settings files in my Django project (base.py, local.py, production.py).
Locally, I'm kicking off the Django dev server via
python manage.py runserver --settings=config.settings.local
How can I tell mod_wsgi to use the same file? I'm testing this on my local apache instance, but will need to figure out the same thing for my production apache instance.
--settings=config.settings.production
Current httpd-vhosts.conf:
WSGIDaemonProcess secureDash python-path=/Users/user/projects/secureDash_project/secureDash python-home=/Users/user/.venvs/securedash_py3.6
WSGIProcessGroup secureDash
WSGIScriptAlias / /Users/user/projects/secureDash_project/config/wsgi.py
Error that I see in the apache log:
ModuleNotFoundError: No module named 'secureDash.settings'
Django 1.11
mod_wsgi-4.5.15
Apache 2.4.18

You do this by setting the DJANGO_SETTINGS_MODULE environment variable. You can either do this in your Apache vhost configuration, or in the wsgi file itself.

I solved this by adding DJANGO_SETTINGS_MODULE to my secrets.json file in each environment.
secrets.json:
{
"FILENAME": "secrets.json",
"SECRET_KEY": "someSuperSecretiveSecret!",
"DJANGO_SETTINGS_MODULE": "config.settings.local"
}
wsgi.py:
import json
from django.core.exceptions import ImproperlyConfigured
from pathlib import Path
...
# Directory Paths
BASE_DIR = Path(__file__).resolve().parent
# JSON-based secrets module
SECRETS_JSON = str(BASE_DIR) + '/secrets.json'
with open(SECRETS_JSON) as f:
secrets = json.loads(f.read())
def get_secret(setting, secrets=secrets):
'''Get the secret variable or return explicit exception'''
try:
return secrets[setting]
except KeyError:
error_msg = 'Set the {0} environment variable'.format(setting)
raise ImproperlyConfigured(error_msg)
DJANGO_SETTINGS_MODULE = get_secret('DJANGO_SETTINGS_MODULE')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", DJANGO_SETTINGS_MODULE)

Related

Django os.getenv('SECRET_KEY') throwing "The SECRET_KEY setting must not be empty."

I'm setting up Django using os.getenv to prepare it for deploying using Docker but it seems it is not reading the .env file. Any idea why is not reading it?
Here is the setup:
.env
SECRET_KEY=foo
DEBUG=True
ALLOWED_HOSTS=localhost,127.0.0.1
settings.py abstraction
import os
from pathlib import Path
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SECRET_KEY = os.getenv('SECRET_KEY')
DEBUG = os.getenv('DEBUG')
ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS')
You can use python-decouple to get the environment variable stored in the root of your project in a .env file.
from decouple import config
SECRET_KEY = config('SECRET_KEY')
DEBUG = config('DEBUG', default=False, cast=bool)
EMAIL_HOST = config('EMAIL_HOST', default='localhost')
EMAIL_PORT = config('EMAIL_PORT', default=25, cast=int)
Note: for changes to apply you need to restart the server.
I'm using python-dotenv in order to implement dotenv functionality. If you want Django to find your .env file, you need to modify manage.py and wsgi.py files.
# manage.py
import os
import sys
import dotenv
def main():
"""Run administrative tasks."""
# dotenv settings
dotenv.load_dotenv(
os.path.join(os.path.dirname(__file__), '.env')
)
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings')
if os.getenv('DJANGO_SETTINGS_MODULE'):
os.environ['DJANGO_SETTINGS_MODULE'] = os.getenv('DJANGO_SETTINGS_MODULE')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
# wsgi.py
import os
import dotenv
from django.core.wsgi import get_wsgi_application
# dotenv settings
dotenv.load_dotenv(
os.path.join(os.path.dirname(os.path.dirname(__file__)), '.env')
)
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project.settings')
if os.getenv('DJANGO_SETTINGS_MODULE'):
os.environ['DJANGO_SETTINGS_MODULE'] = os.getenv('DJANGO_SETTINGS_MODULE')
application = get_wsgi_application()
When deploying via docker-compose, you can specify in the docker-compose file.bml in the container settings [web]:
web:
...
env_file:
- ./.env
It worked for me without using additional packages. In my case , the file .env is located in the directory where docker-compose.yml is located
To get environment variables from docker or from the AWS Elastic Beanstalk i use
os.environ.get('SECRET_KEY'), this is generally more reliable than os.environ['SECRET_KEY']

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.

Django webfaction deployment: How to change the default directory of your project

im deploying my django project, named "Hesperides" under a webfaction server. The default project's folder in webfaction it's "myproject" so Im having this error:
ImportError: Could not import settings 'myproject.settings' (Is it on sys.path?): No module named myproject.settings
The question is: How can i do to import the settings from Hesperides.settings instead from myproject.settings? Thanks for your time.
My httpd.conf:
WSGIDaemonProcess jinn processes=2 threads=12 python-path=/home/zeioth/webapps/jinn:/home/zeioth/webapps/jinn/myproject:/home/zeioth/webapps/jinn/lib/python2.7:
WSGIScriptAlias / /home/zeioth/webapps/jinn/myproject/Hesperides/wsgi.py
My wsgi.py
import os
import sys
from django.core.handlers.wsgi import WSGIHandler
os.environ['DJANGO_SETTINGS_MODULE'] = 'Hesperides.settings'
application = WSGIHandler()
My folder tree:
jinn
--apache2
-bin
--conf
-httpd.conf
-mime.types
-lib
-logs
-modules
-bin
-lib
--myproject
-manage.py
--Hesperides
-wsgi.py
-urls.py
-__init__.py
-settings.py
-apps
PROBLEM SOLVED: These are the steps to change your project directory under webfaction:
1-in httpd.conf in webapps/yourappname/apache2 set:
WSGIScriptAlias / /home/zeioth/webapps/jinn/myproject/yourNewName/wsgi.py
2-in your app's manage.py set:
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "yourNewName.settings")
3-in your app's wsgi.py set:
os.environ['DJANGO_SETTINGS_MODULE'] = 'yourNewName.settings'
4-In your app's settings.py set:
WSGI_APPLICATION ='yourNewName.wsgi.application'
Now, runserver should work, or tell you if you need to install some module.
Note that when you have use WSGIDaemonProcess you need to have a corresponding WSGIProcessGroup directive else your WSGI application will not be delegated to run in that daemon process group. WebFaction was generating a broken configuration file for a while if I remember. That or you copied this from one of the blog posts out there which have broken examples. Read:
http://code.google.com/p/modwsgi/wiki/CheckingYourInstallation#Embedded_Or_Daemon_Mode

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
[...]