django-configurations icon indicating copy to clipboard operation
django-configurations copied to clipboard

Booleans with default don't respect Environment Variable

Open striveforbest opened this issue 6 years ago • 5 comments

Issue:

I would like to have a flag determining whether django-debug-toolbar should show up. I tried multiple options provided in the docs but my value still ends up being False. I am using a .env per your example.

Approach:

  1. Set a settings variable defaulting to False: DEBUG_TOOLBAR_ENABLED = values.BooleanValue(False)
  2. Set environment variable for local development in my DOTENV file (.env): DJANGO_DEBUG_TOOLBAR_ENABLED=True (also tried true, "True", 1, and "true")

Code

.env:

DJANGO_SETTINGS_MODULE=fuweb.settings
DJANGO_CONFIGURATION=Development

DJANGO_ALLOWED_HOSTS=*
DJANGO_SECRET_KEY=1234
DJANGO_DEBUG_TOOLBAR_ENABLED=true

settings.py:

class Common(Configuration):

    PROJECT_DIR = Path(__file__).parent
    BASE_DIR = PROJECT_DIR.parent
    DOTENV = Path.joinpath(BASE_DIR, '.env')
    SECRET_KEY = values.SecretValue()
    DEBUG = values.BooleanValue(True)

    DEBUG_TOOLBAR_ENABLED = values.BooleanValue(False)
    print('env var:', os.environ.get('DJANGO_DEBUG_TOOLBAR_ENABLED'))
    print('settings var:', DEBUG_TOOLBAR_ENABLED)

Console output:

$ ./manage.py runserver

env var: true
settings var: False

striveforbest avatar Feb 14 '19 22:02 striveforbest

To add to this, the Boolean values are cast correctly. Below is a BooleanValue class from django-configurations source code, I added print() statements for debugging.

class BooleanValue(Value):
    true_values = ('yes', 'y', 'true', '1')
    false_values = ('no', 'n', 'false', '0', '')

    def __init__(self, *args, **kwargs):
        super(BooleanValue, self).__init__(*args, **kwargs)
        if self.default not in (True, False):
            raise ValueError('Default value {0!r} is not a boolean value'.format(self.default))

    def to_python(self, value):
        normalized_value = value.strip().lower()
        if normalized_value in self.true_values:
            print(True)
            return True
        elif normalized_value in self.false_values:
            print(False)
            return False
        else:
            raise ValueError('Cannot interpret boolean value {0!r}'.format(value))

The output of my print() statements is correct as expected. But it seems like the settings file is evaluated before the value is pulled from the environment variable and cast to the correct Boolean value. That being said, you cannot rely on the Boolean value to use in conditional logic in settings. The code below never executes regardless of my environment variable value because it always uses the default value.

if DEBUG_TOOLBAR_ENABLED:
    print('I swear, it is TRUE!')

Any suggestions?

striveforbest avatar Mar 12 '19 18:03 striveforbest

@StriveForBest Checking here how does it work I saw that django-configurations does something like this:

#.env
DJANGO_DEBUG=False
# settings.py
import os

from configurations import Configuration, values


class Base(Configuration):
    DEBUG = values.BooleanValue(True)


class Development(Base):
    DOTENV = os.path.join(Base.BASE_DIR, '.env')

Turns into something like this at run time:

# settings.py
import os

from configurations import Configuration, values


class Base(Configuration):
    DEBUG = values.BooleanValue(True)


class Development(Base):
    DOTENV = os.path.join(Base.BASE_DIR, '.env')

DEBUG = False

So it is possible to use django.conf.settings .'

from django.conf import settings
if settings.DEBUG:
    print(''I swear, it's true!)

hugobrilhante avatar Apr 06 '20 00:04 hugobrilhante

@hugobrilhante That is not what I'm talking about, I mean relying on Boolean values withing the settings.py, even within the same settings class.

striveforbest avatar Jul 01 '20 16:07 striveforbest

Understand. have you tried it environ_name

hugobrilhante avatar Jun 30 '21 20:06 hugobrilhante

Understand. have you tried it environ_name

that doesn't help. i found two reliable ways. either using

if VAR_NAME.value:
     pass

or put the logic into post_setup hook, like so (I believe this is the more correct way).

class Development(Common):
    @classmethod
    def post_setup(cls):
        """ Set up django-toolbar. """
        if cls.DEBUG_TOOLBAR_ENABLED:
            cls.INSTALLED_APPS += [
                'debug_toolbar',
            ]

striveforbest avatar Jun 30 '21 20:06 striveforbest