Package: django-cogwheels
What is django-cogwheels?The aim ofdjango-cogwheelsis 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 upProperly accounts for different audiences (the ‘app developer’ and ‘app user’)Will work as well for 100 apps setting as it will for 5Only makes things complicated when absolutely necessaryGive 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’ssetting_changedsignal, so that cached values are cleared whenoverride_settingsis 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 guideInstall the package using pip:pip install django-cogwheelscdinto your project’s root app directory:cd your-django-project/yourproject/Create aconfapp using the cogwheels Django app template:django-admin.py startapp conf --template=https://github.com/ababic/cogwheels-conf-app/zipball/masterOpen upyourproject/conf/defaults.py, and add your setting values like so:# You can add settings for any type of valueMAX_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'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'For settings that refer to Django models, you can use the settings helper’s specialmodelsattribute 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’sdjango.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.For settings that refer to python modules, you can use the settings helper’s specialmodulesattribute 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’simportlib.import_module()method is called, and the result is cached so that repeat requests for same module are handled quickly and efficiently.For settings that refer to classes, functions, or other importable python objects, you can use the settings helper’s specialobjectsattribute 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()
FalseBehind the scenes, python’simportlib.import_module()method is called, and the result is cached so that repeat requests for same object are handled quickly and efficiently.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.pyYOURAPP_MAX_ITEMS_PER_ORDER=2YOURAPP_ORDER_ITEM_MODEL='userproject_orders.CustomOrderItem'YOURAPP_DISCOUNTS_BACKEND='userproject.discounts.custom_discount_backend'YOURAPP_ORDER_FORM_CLASS='userproject.orders.forms.CustomOrderForm'You may noticed that the above variable names are all prefixed withYOURAPP_. 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 aprefixattribute on your settings helper class, like so:# yourapp/conf/settings.pyclassMyAppSettingsHelper(BaseAppSettingsHelper):prefix='CUSTOM'# No need for a trailing underscore here>>> from yourproject.conf import settings
>>> settings.get_prefix()
'CUSTOM_'Frequently asked questions1. Are there any example implmentations ofdjango-cogwheelsthat I can look at?Sure thing.wagtailmenususes cogwheels to manage it’s app settings. See:https://github.com/rkhleics/wagtailmenus/tree/master/wagtailmenusYou might also want to check out thetestsapp within cogwheels itself, which includes lots of examples:https://github.com/ababic/django-cogwheels/tree/master/cogwheels/tests2. Dodefaults.pyandsettings.pyhave to live in aconfapp?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 keepdefaults.pyandsettings.pyin the same directory, things should work just fine out of the box.If you wantdefaults.pyandsettings.pyto live in separate places,cogwheelssupports that too. But, you’ll have to set thedefaults_pathattribute on your settings helper class, so that it knows where to find the default values. For example:# yourapp/some_directory/settings.pyclassMyAppSettingsHelper(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 thetestsapp’s setting helper definition:https://github.com/ababic/django-cogwheels/blob/master/cogwheels/tests/conf/settings.py4. How do specify validation rules for certain settings?The only validation thatcogwheelsperforms is on setting values that are supposed to reference Django models and other importables, and this validation is only triggered when you usesettings.models.SETTING_NAME,settings.modules.SETTING_NAMEorsettings.objects.SETTING_NAMEin 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 insettings.pyall about?Ahh, yes. Thesys.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 incogwheelscase, 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.htmlCompatibilityThe current version is tested for compatiblily with the following:Django versions 1.11 to 2.2Python versions 3.4 to 3.8