rantanplan
rantanplan

Reputation: 7450

Python fabric "local" function does not respect environment variable DJANGO_SETTINGS_MODULE

I have the following scheme for my project's settings:

myproject/
   app1/
   app2/
   appN/
   settings/
      __init__.py
      base.py
      devel.py
      production.py

In my local environment I have inside the virtualenvwrapper's postactivate script:

myproject_root=/home/rantanplan/Projects/repos/myproject
cd $myproject_root
export DJANGO_SETTINGS_MODULE=myproject.settings.devel

So that when I do workon myproject it will change to the project's root dir and set the active DJANGO_SETTINGS_MODULE I want.

This is fine for django and all the commands(like python manage.py syncdb) work.

Now on the other hand I have this fabric task:

@task
def syncdb():
    local('python manage.py syncdb --noinput')

This used to work fine when I had a simple settings.py file, but when I changed to the above scheme it raises this exception:

django.core.exceptions.ImproperlyConfigured: settings.DATABASES is improperly configured. Please supply the ENGINE value. Check settings documentation for more details.

Fatal error: local() encountered an error (return code 1) while executing 'python manage.py syncdb --noinput'

Aborting.

Some additional notes:

So what am I doing wrong here and how do you propose I should go about solving this issue?


To spare you the trouble I should say that I know I can solve this by doing:

def syncdb():
    with prefix('export DJANGO_SETTINGS_MODULE=myproject.settings.devel'):
        local('python manage.py syncdb --noinput')

but I'd rather avoid the use of prefix if I can.

Also I know I could do, as hynekcer suggests:

@task
def syncdb():
    local('python manage.py syncdb --settings=myproject.settings.devel --noinput')

but I really want to know why local does not respect the DJANGO_SETTINGS_MODULE and why settings_module does not work as advertised.

Upvotes: 3

Views: 1564

Answers (2)

rantanplan
rantanplan

Reputation: 7450

Well I found the problem but I'm not sure what to make of it.

First of all it seems that I didn't reveal all the necessary bits of information.

Although my django project's structure is exactly as I described, my fabric structure is a bit more complex.

In essence I follow some patterns from this part of fabric's documentation

The complete structure is described below:

deployment/
  __init__.py
  fabric/
    __init__.py
    database.py
    repo.py
    services.py
myproject/
  app1/
  app2/
  appN/
manage.py
fabfile.py

Inside the deployment/fabric/database.py I had this code:

django.settings_module('myproject.settings.devel')

@task
def syncdb():
    local('python manage.py syncdb --noinput')

And inside my fabfile.py I had all my imports:

from deployment.fabric.database import dropdb, createdb, syncdb, createuser
from deployment.fabric.something import blahblah

For some reason, that I can't seem to grasp right now, inside the fabfile.py the setting of the DJANGO_SETTINGS_MODULE(which was happening inside the deployment/fabric/database.py) is not retained.

At first I came to the false realization that os.environ actions do not persist across modules! But this is not true, as I immediately constructed a similar scenario outside my django project and invalidated my false premise.

Then I checked fabric's local function and saw that it essentially is a wrapper over subprocess.Popen('...', shell=True). So I tested my previous experiment with subprocess.Popen and it still retained the environment variables across modules.

I don't know if it has to do with fabric's magic tasks import or there is something fundamental I don't grasp but any of the below methods will solve the issue.

1) Use the prefix context manager

def syncdb():
    with prefix('export DJANGO_SETTINGS_MODULE=myproject.settings.devel'):
        local('python manage.py syncdb --noinput')

2) Append a --settings value in the local command (as described by hynekcer)

@task
def syncdb():
    local('python manage.py syncdb --settings=myproject.settings.devel --noinput')

3) Include the settings_module call inside the task(Although it kinda invalidates its purpose).

@task
def syncdb():
    django.settings_module('myproject.settings.devel')
    local('python manage.py syncdb --noinput')

Upvotes: 1

hynekcer
hynekcer

Reputation: 15548

You can use settings option. It has precedence over DJANGO_SETTINGS_MODULE variable.

@task
def syncdb():
    local('python manage.py syncdb --settings=myproject.settings.devel --noinput')

Upvotes: 1

Related Questions