Giter Site home page Giter Site logo

Comments (4)

matthewwithanm avatar matthewwithanm commented on June 7, 2024

Hey @bartek! There are two ways to do this: 1) change which class you select or 2) change the class itself.

Like you suggested, the first way would require you to update environment variables. I don't think you would need any fancy import magic; just explicitly declare which environment you're using:

# DJANGO_SETTINGS_MODULE = 'local_settings.py'
# DJANGO_CONFIGURATIONS = 'LocalSettings'

class LocalSettings(SiteSettings, Production):
    pass

Since your local settings (presumably) aren't part of your repository, this shouldn't be a problem.

The second option (changing the class definition) is more like the traditional use of local_settings (where the settings module contains different variables depending on the presence of a local_settings module. One simple way to do this is to use a LocalSettings mixin:

# local_settings.py

class LocalSettings(object):
    WHATEVER = 'hello'
# settings.py

try:
    from local_settings import LocalSettings
except ImportError:
    class LocalSettings: pass

class Production(LocalSettings, SiteSettings):
    pass

This way, you wouldn't have to change your environment variables. Cons to this approach are 1) you need to watch out for MRO and 2) depending on the number of settings modules, you may have to mix LocalSettings into a lot of classes.

Another option would be to use a factory; that is, to point DJANGO_CONFIGURATION to a function that instantiates the settings object and do your local settings manipulation there (either by directly modifying the instance or by creating a new type that mixes in LocalSettings and instantiating that). The con to this approach is that you've now moved the logic for selecting your settings class into your code which may or may not be desirable. Of course, you could have the factory method look at yet another environment variable to decide which class to instantiate initially.

Because we're just dealing with plain old classes, there really are a lot of options. (You could also use a meta class to mix in the local settings, for example.) It's hard to say which is ideal.

Because this seems like such a common case, maybe a new feature is in order (that's really a question for @jezdez). I don't know exactly what form it would take—perhaps a function that gets passed the settings module post-load? The downside would be that it would probably require more repeated configuration (at all of the app entry points to specify the "filter function"—though using some kind of convention is also an option), but at least there would be a documented way to do this that didn't require too much headscratching.

from django-configurations.

vangale avatar vangale commented on June 7, 2024

I have something similar: an app with 4 production sites, 2 test sites, multiple development sites on developer machines, and test and production sites for API (tastypie based). I'm finally happy now that I have a single settings.py using django-configurations.

I achieved this by using a lot of mixins to break the configuration down to a more granular level. So for example, my "site" classes are declared like this:

class Customer1Site(Sentry, PyLibMCCache, ProductionDatabase, ProductionEmail, Base):
    SITE_ID = X
    COOKIE_DOMAIN = "system.customer1.com"
    MEDIA_URL = 'https://media.customer1.com/media/'
    STATIC_URL = 'https://media.customer1.com/static/'

class DevSite(NoSentry, DebugMode, DebugToolbar, LocalPyLibMCCache, TestDatabase, DebugEmail, Base):
    SITE_ID = X
    MEDIA_URL = '/media/'
    STATIC_URL = '/static/'
    # Production and test sites are all SSL, so this overrides "Base" if the dev uses runserver.
    SESSION_COOKIE_SECURE = False
    # These also added for runserver
    @property
    def INSTALLED_APPS(self):
        return super(DevSite, self).INSTALLED_APPS + ('django.contrib.staticfiles',)
    STATICFILES_DIRS = (
        ROOT('app/media/'),
    )

class Test1Site(Sentry, DebugMode, DebugToolbar, PyLibMCCache, TestDatabase, DebugEmail, Base):
    SITE_ID = X
    COOKIE_DOMAIN ="test.customer1.com"
    MEDIA_URL = 'https://test-media.customer1.com/media/'
    STATIC_URL = 'https://test-media.customer1.com/static/'
    SENTRY_DSN = "https://xxxx"   # Override production value in Sentry mixin
    @property
    def CACHES(self):
        super(Test1Site, self).CACHES['default']['KEY_PREFIX'] = 'TEST'
        return super(Test1Site, self).CACHES

class APISite(NoSentry, PyLibMCCache, ProductionDatabase, ProductionEmail, APISettings, Base):
    SITE_ID = X
    @property
    def CACHES(self):
        super(APISite, self).CACHES['default']['KEY_PREFIX'] = 'API'
        return super(APISite, self).CACHES

from django-configurations.

bartek avatar bartek commented on June 7, 2024

Thanks for the replies @matthewwithanm and @vangale!

It's always refreshing to bounce ideas off other brains. My biggest concern was having to either add a bunch of LocalSettings mixins into each settings.sites setting, or having a large localsettings that declares them all. Using some of these ideas I came up with this very rough approach at the moment:

In settings.sites.mysite (and every other site module in settings.sites) I've added the following:

from settings.sites import register_configurations_hook

class SiteSettings(object):
     # ...

locals().update(register_configurations_hook(SiteSettings))

Then register_configurations_hook is plainly simple like so:

def register_configurations_hook(settingsClass):
    from settings import Development, Production

    try:
        from settings.localsettings import LocalSettings
    except ImportError:
        class LocalSettings: pass

    class Production(LocalSettings, settingsClass, Production):
        pass

    class Development(LocalSettings, settingsClass, Development):
        pass

    return {
        'Production': Production,
        'Development': Development,
    }

This definitely places some of the logic of configuration into the code but I find it fairly sane. I'm not a huge fan of the use of locals() but it's a quick example to get the idea working, I could probably hash it out some more.

Then, my localsettings.py is simply a plain class that can do whatever. Keeps that part clean.

class LocalSettings(object):
     MEDIA_URL = 'http://localhost:8080/media-server/'
     # etc.

Thanks again for your contributions. I need to definitely think about this a bit more before I'm happy with a solution but yay for this community! :-)

from django-configurations.

jezdez avatar jezdez commented on June 7, 2024

One way of handling "local" settings (per user settings) is the one that I've implemented a while ago for localshop:

import os
from django.core.exceptions import ImproperlyConfigured
from configurations.utils import uppercase_attributes


def FileSettings(path, name=None, required=True):
    """
    A simply factory function to return a base class settings
    class populated from a Python file located at the given path.

    Use it like this::

        from configurations import Settings

        class Acme(FileSettings('~/.acme/acme.conf.py'), Settings):
            pass

    You can make reading the file optional by passing in a ``required``
    parameter::

        from configurations import Settings

        local_settings = '~/.acme/acme.conf.py'

        class Acme(FileSettings(local_settings, required=False), Settings):
            pass        

    """
    if name is None:
        name = __name__
    path = os.path.expanduser(path)
    mod = imp.new_module('%s.local' % name)
    mod.__file__ = path

    class Holder(object):
        pass

    try:
        execfile(path, mod.__dict__)
    except IOError, e:
        if required:
            raise ImproperlyConfigured(
                "Notice: Unable to load required configuration file %s (%s), "
                "using default settings\n\n" % (path, e.strerror))
        return Holder

    for name, value in uppercase_attributes(mod).items():
        setattr(Holder, name, value)

    return Holder

I'm not sure which strategy is best though, I'd rather not make that decision for developers.

from django-configurations.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.