Skip to main content

A consistent, user-friendly solution for adding app-specific settings your Django package, reusable app or framework.

Project description

Django Cogwheels Build Status PyPi Version Code coverage

What is django-cogwheels?

The aim of django-cogwheels is to create a standardised, well-tested approach for allowing users of an app to override default behaviour, by overriding things in their project’s Django settings.

There are other apps out there that try to solve this problem, but it was important for me to create a solution that would cater well for deprecation of settings, as this is something I find myself having to do regularly in apps I maintain. It was also important for me to create something that:

  • Is super easy to set up
  • Properly accounts for different audiences (the ‘app developer’ and ‘app user’)
  • Will work as well for 100 apps setting as it will for 5
  • Only makes things complicated when absolutely necessary

Give your users the flexibility they deserve, and allow them to:

  • Override basic python type values such as: strings, integers, booleans, decimals and floats.
  • Override structured python type values such as: lists, tuples and dictionaries.
  • Use custom Django models in place of the ones you provide.
  • Use custom python classes, objects or entire modules in place of the ones you provide.

Goodness for app developers!

  • A stable, documented, standardised approach for implementing overridable app-specific settings.
  • Clearly define and communicate the deprecation status of app settings, giving you the flexibility to rename, replace or flag settings for removal over your project’s lifecycle. User overrides defined using old setting names remain available to you, allowing you to continue to support them during the deprecation period.
  • Helpful, consistent error messages when default values provided for models, modules or other overridable object settings are invalid.
  • Cached imports for speedy access to models, modules and other importable python objects.
  • Plays nicely with Django’s test framework (subscribes to Django’s setting_changed signal, so that cached values are cleared when override_settings is used).

Goodness for app users!

  • Helpful, consistent error messages when their Model, Class, method or module override settings are incorrectly formatted, or cannot be imported.
  • Helpful, consistent deprecation warnings when they are overriding a setting that has been renamed, replaced or flagged for removal.

Quick start guide

  1. Install the package using pip:

    pip install django-cogwheels
    
  2. cd into your project’s root app directory:

    cd your-django-project/yourproject/
    
  3. Create a conf app using the cogwheels Django app template:

    django-admin.py startapp conf --template=https://github.com/ababic/cogwheels-conf-app/zipball/master
    
  4. Open up yourproject/conf/defaults.py, and add your setting values like so:

    # You can add settings for any type of value
    MAX_ITEMS_PER_ORDER = 5
    
    # For settings that refer to models, use a string in the format 'app_name.Model'
    ORDER_ITEM_MODEL = 'yourproject.SimpleOrderItem'
    
    # For settings that refer to a python module, use an 'import path' string, like so:
    DISCOUNTS_BACKEND = 'yourproject.discount_backends.simple'
    
    # For settings that refer to classes, methods, or other python objects from a
    # python module, use an 'object import path' string, like so:
    ORDER_FORM_CLASS = 'yourproject.forms.OrderForm'
    
  5. To use setting values in your app, simply import the settings helper, and reference the relevant setting as an attribute, like this:

    >>> from yourproject.conf import settings
    
    >>> settings.MAX_ITEMS_PER_ORDER
    5
    
    >>> settings.ORDER_ITEM_MODEL
    'yourproject.SimpleOrderItem'
    
    >>> settings.DISCOUNTS_BACKEND
    'yourproject.discount_backends.simple'
    
    >>> settings.ORDER_FORM_CLASS
    'yourproject.forms.OrderForm'
    
  6. For settings that refer to Django models, you can use the settings helper’s special models attribute to access model classes themselves, rather than just the string value. For example:

    >>> from yourproject.conf import settings
    
    >>> model = settings.models.ORDER_ITEM_MODEL
    yourproject.models.SimpleOrderItem
    
    >>> obj = model(id=1, product='test product', quantity=15)
    >>> obj.save()
    
    >>> print(model.objects.all())
    <QuerySet [<SimpleOrderItem: SimpleOrderItem object (1)>]>
    

    Behind the scenes, Django’s django.apps.apps.get_model() method is called, and the result is cached so that repeat requests for the same model are handled quickly and efficiently.

  7. For settings that refer to python modules, you can use the settings helper’s special modules attribute to access the modules themselves, instead of an import path string:

    >>> from yourproject.conf import settings
    
    >>> module = settings.modules.DISCOUNTS_BACKEND
    <module 'yourproject.discount_backends.simple' from '/system/path/to/your-django-project/yourproject/discount_backends/simple.py'>
    

    Behind the scenes, python’s importlib.import_module() method is called, and the result is cached so that repeat requests for same module are handled quickly and efficiently.

  8. For settings that refer to classes, functions, or other importable python objects, you can use the settings helper’s special objects attribute to access those objects, instead of an import path string:

    >>> from yourproject.conf import settings
    
    >>> form_class = settings.objects.ORDER_FORM_CLASS
    yourproject.formsOrderForm
    
    >>> form = form_class(data={})
    >>> form.is_valid()
    False
    

    Behind the scenes, python’s importlib.import_module() method is called, and the result is cached so that repeat requests for same object are handled quickly and efficiently.

  9. Users of your app can now override any of the default values by adding alternative values to their project’s Django settings module. For example:

    # userproject/settings/base.py
    
    YOURAPP_MAX_ITEMS_PER_ORDER = 2
    
    YOURAPP_ORDER_ITEM_MODEL = 'userproject_orders.CustomOrderItem'
    
    YOURAPP_DISCOUNTS_BACKEND = 'userproject.discounts.custom_discount_backend'
    
    YOURAPP_ORDER_FORM_CLASS = 'userproject.orders.forms.CustomOrderForm'
    
  10. You may noticed that the above variable names are all prefixed with YOURAPP_. This prefix will differ for your app, depending on the package name.

    This ‘namespacing’ of settings is important. Not only does it helps users of your app to remember which app their override settings are for, but it also helps to prevent setting name clashes between apps.

    You can find out what the prefix is for your app by doing:

    >>> from yourproject.conf import settings
    >>> settings.get_prefix()
    'YOURPROJECT_'
    

    You can change this prefix to whatever you like by setting a prefix attribute on your settings helper class, like so:

    # yourapp/conf/settings.py
    
    class MyAppSettingsHelper(BaseAppSettingsHelper):
        prefix = 'CUSTOM'  # No need for a trailing underscore here
    
    >>> from yourproject.conf import settings
    >>> settings.get_prefix()
    'CUSTOM_'
    

Frequently asked questions

1. Are there any example implmentations of django-cogwheels that I can look at?

Sure thing.

wagtailmenus uses cogwheels to manage it’s app settings. See: https://github.com/rkhleics/wagtailmenus/tree/master/wagtailmenus

You might also want to check out the tests app within cogwheels itself, which includes lots of examples: https://github.com/ababic/django-cogwheels/tree/master/cogwheels/tests

2. Do defaults.py and settings.py have to live in a conf app?

No. This is just a recommendation. Everyone has their own preferences for how they structure their projects, and that’s all well and good. So long as you keep defaults.py and settings.py in the same directory, things should work just fine out of the box.

If you want defaults.py and settings.py to live in separate places, cogwheels supports that too. But, you’ll have to set the defaults_path attribute on your settings helper class, so that it knows where to find the default values. For example:

# yourapp/some_directory/settings.py

class MyAppSettingsHelper(BaseAppSettingsHelper):
    defaults_path = 'yourapp.some_other_place.defaults'

3. You mentioned support for setting deprecation. How does that work?

More complete documentation will be added soon. In the meantime, if you’re curious about what deprecation definitions look like, you may want to check out the tests app’s setting helper definition: https://github.com/ababic/django-cogwheels/blob/master/cogwheels/tests/conf/settings.py

4. How do specify validation rules for certain settings?

The only validation that cogwheels performs is on setting values that are supposed to reference Django models and other importables, and this validation is only triggered when you use settings.models.SETTING_NAME, settings.modules.SETTING_NAME or settings.objects.SETTING_NAME in your code to import and access the object.

There’s currently no way to configure ``cogwheels`` to apply validation to other setting values.

I do intend to support such a thing future versions, but I can’t make any promises as to when.

If this puts you off, keep in mind that it’s not in anybody’s interest for developers to purposefully use inappropriate override values for settings. So long as your documentation explains the rules/boundaries for expected values well enough, issues should be very rare.

5. What’s that last line in settings.py all about?

Ahh, yes. The sys.modules[__name__] = MyAppSettingsHelper() bit. I understand that some developers might think this dirty/hacky/unpythonic/whatever. I have to admit, I was unsure about it for a while, too.

I’ll agree that it is somewhat ‘uncommon’ to see this code in use. Perhaps because it’s not particularly useful in a lot situations, or perhaps because using such features incorrectly can break things in strange, hard-to-debug ways. But, support for this hack is not going anywhere, and in cogwheels case, it’s useful, as it removes the need to instantiate things in __init__.py (which I dislike for a number of reasons).

If you’re still not reassured, perhaps Guido van Rossum (Founder of Python) can put your mind at rest? https://mail.python.org/pipermail/python-ideas/2012-May/014969.html

Compatibility

The current version is tested for compatiblily with the following:

  • Django versions 1.11 to 2.1
  • Python versions 3.4 to 3.7

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Filename, size & hash SHA256 hash help File type Python version Upload date
django_cogwheels-0.2-py3-none-any.whl (31.7 kB) Copy SHA256 hash SHA256 Wheel py3
django-cogwheels-0.2.tar.gz (27.2 kB) Copy SHA256 hash SHA256 Source None

Supported by

Elastic Elastic Search Pingdom Pingdom Monitoring Google Google BigQuery Sentry Sentry Error logging AWS AWS Cloud computing DataDog DataDog Monitoring Fastly Fastly CDN SignalFx SignalFx Supporter DigiCert DigiCert EV certificate StatusPage StatusPage Status page