Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
A consistent, user-friendly solution for adding app-specific settings your Django package, reusable app or framework.
.. image:: https://raw.githubusercontent.com/ababic/django-cogwheels/master/docs/source/_static/django-cogwheels-logo.png :alt: Django Cogwheels
.. image:: https://travis-ci.com/ababic/django-cogwheels.svg?branch=master :alt: Build Status :target: https://travis-ci.com/ababic/django-cogwheels
.. image:: https://img.shields.io/pypi/v/django-cogwheels.svg :alt: PyPi Version :target: https://pypi.python.org/pypi/django-cogwheels
.. image:: https://codecov.io/gh/ababic/django-cogwheels/branch/master/graph/badge.svg :alt: Code coverage :target: https://codecov.io/gh/ababic/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:
Give your users the flexibility they deserve, and allow them to:
setting_changed
signal, so that cached values are cleared when override_settings
is used).Install the package using pip:
.. code-block:: console
pip install django-cogwheels
cd
into your project's root app directory:
.. code-block:: console
cd your-django-project/yourproject/
Create a conf
app using the cogwheels Django app template:
.. code-block:: console
django-admin.py startapp conf --template=https://github.com/ababic/cogwheels-conf-app/zipball/master
Open up yourproject/conf/defaults.py
, and add your setting values like so:
.. code-block:: python
# 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'
To use setting values in your app, simply import the settings helper, and reference the relevant setting as an attribute, like this:
.. code-block:: console
>>> 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 special models
attribute to access model classes themselves, rather than just the string value. For example:
.. code-block:: console
>>> 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.
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:
.. code-block:: console
>>> 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.
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:
.. code-block:: console
>>> 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.
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:
.. code-block:: python
# 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'
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:
.. code-block:: console
>>> 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:
.. code-block:: python
# yourapp/conf/settings.py
class MyAppSettingsHelper(BaseAppSettingsHelper):
prefix = 'CUSTOM' # No need for a trailing underscore here
.. code-block:: console
>>> from yourproject.conf import settings
>>> settings.get_prefix()
'CUSTOM_'
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
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:
.. code-block:: python
# yourapp/some_directory/settings.py
class MyAppSettingsHelper(BaseAppSettingsHelper):
defaults_path = 'yourapp.some_other_place.defaults'
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
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.
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
The current version is tested for compatiblily with the following:
FAQs
A consistent, user-friendly solution for adding app-specific settings your Django package, reusable app or framework.
We found that django-cogwheels demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.