Skip to main content

Stay informed of it

Project description

Django Dynamic Settings

Build status PyPI PyPI - Django version PyPI - Python version PyPI - License

Django Dynamic Settings allows you to create & use dynamic settings backed by a database.

Installation

Installation using pip:

pip install dj-dynamic-settings

dj_dynamic_settings app has to be added to INSTALLED_APPS and migrate command has to be run.

INSTALLED_APPS = (
    # other apps here...
    "dj_dynamic_settings",
)

dj_dynamic_settings.urls must be included to a desired url path.

urlpatterns = [
    ...,
    url(r"^api/v1/", include("dj_dynamic_settings.urls")),
]

Setting class must be defined & registered. Please make sure that this class' module runs whenever the application runs.

from dj_dynamic_settings.registry import BaseSetting, registry
from dj_dynamic_settings.validators import TypeValidator


@registry.register
class FeatureActive(BaseSetting):
    key = "FEATURE_ACTIVE"
    validators = [TypeValidator(bool)]
    default = False
    description = "Flag for Feature X"

Create Setting instance using view.

import requests

requests.post(
    url="https://your-app.com/api/v1/dynamic_settings/",
    headers={
        "Authorization": "Token <secret-login-token>",
    },
    json={
        "key": "FEATURE_ACTIVE",
        "value": True,
        "is_active": True,
    }
)

Access this setting as in django.conf.settings

from dj_dynamic_settings.conf import settings


settings.FEATURE_ACTIVE  # True

Create / Update Triggers

To fire a callback method when a specific setting value updated or created, you can implement post_save_actions in BaseSetting inherited class

Following example shows how to implement post_save_actions method.

The callback method will be called with following kwargs:

key=instance.key
value=instance.value
created=created # is create operation

Note: post_save_actions returns an array, so you can add multiple callback methods. These callback methods will be called synchronously.

class PostUpdateTestConfiguration(BaseSetting):
    key = "X_FEATURE_POST_UPDATE"
    validators = [...]

    @classmethod
    def post_save_actions(cls):
        return [
            on_data_updated,
        ]

def on_data_updated(*args, **kwargs):
    pass

Testing Tools

override_settings()

You can override a setting for a test method or test class.

from dj_dynamic_settings.utils import override_settings
from django.test import TestCase

@override_settings(SOME_SETTING="some_setting")
class FeatureTestCase(TestCase):

    @override_settings(SOME_OTHER_SETTING="SOME_OTHER_SETTING")
    def test_feature(self):
        # Some stuff
        pass

    
    def test_feature_x(self):
        with override_settings(SOME_OTHER_SETTING="SOME_OTHER_SETTING"):
            # Some stuff
            pass

Selective Field-Level Encryption

dj-dynamic-settings supports selective Fernet encryption of sensitive field values. Configuration is two-pronged:

  1. Code-defined defaults via Django setting:
DYNAMIC_SETTINGS_ENCRYPTED_FIELDS = ["password", "api_key", "token"]
  1. Runtime via the /encrypted_keys/ REST API or admin tooling. Admins POST/DELETE entries against the EncryptedKey endpoint to add/remove encrypted fields without a code deploy.

Both sources are merged via a frozenset union — duplicates dedup automatically.

Setting value JSON content is scanned recursively:

  • If the Setting's own key is in the active field-name set, the entire value is encrypted (scalar case).
  • Inside dicts (at any nesting depth), keys matching the active set have their values encrypted.
  • Inside lists, each element is recursed.

Decryption is transparent — your code reads settings.X_FOO and gets plaintext, no changes required:

from dj_dynamic_settings.conf import settings

settings.X_PAYMENT_GATEWAY  # returns plaintext, decrypted on the fly

Cache (Django cache framework) stores ciphertext only; decryption happens at return time. API GET responses replace encrypted fields with "***ENCRYPTED***" (configurable via DYNAMIC_SETTINGS_ENCRYPTION_MASK).

Encryption key:

  • Default: derived from Django's SECRET_KEY via SHA256 → urlsafe base64. Zero setup required.
  • Optional override: set DYNAMIC_SETTINGS_ENCRYPTION_KEY to a stable 32-byte urlsafe base64 Fernet key. Recommended if SECRET_KEY is ever rotated.

When an EncryptedKey row is created or deleted through the API, all existing Settings are scanned and re-encrypted or decrypted symmetrically, atomically within the request. See Encrypting existing data below for the non-API path and the immutability of field_name.

Encrypting existing data

The DYNAMIC_SETTINGS_ENCRYPTED_FIELDS setting is the source of truth for which fields are encrypted at rest. Reconcile the EncryptedKey table to match it with:

python manage.py sync_encrypted_fields            # apply the reconcile
python manage.py sync_encrypted_fields --dry-run  # print the plan, write nothing

The command mirrors the table to the list, in one transaction:

  • A field newly added to the list → an EncryptedKey row is created and all existing matching Setting data is encrypted.
  • A field removed from the list → existing data is decrypted at rest and the row is deleted (a WARNING is logged for each decrypted field).

Run it once per deploy, after migrate. On Akinon Cloud, add it to the akinon.json release script:

"release": "python manage.py migrate --no-input && python manage.py sync_encrypted_fields"

The list is authoritative for the entire EncryptedKey table. Any row whose field_name is not in DYNAMIC_SETTINGS_ENCRYPTED_FIELDS is removed and its data decrypted on the next run — including rows created through the POST /encrypted_keys API. A project that runs this command should manage encrypted fields exclusively through the settings list, not the API.

Removing a field decrypts data at rest. An accidental edit to the list will decrypt sensitive values on the next deploy. Use --dry-run to preview, and review list changes carefully.

field_name is immutable on an existing EncryptedKey (a PATCH/PUT changing it returns HTTP 400).

Download files

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

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dj_dynamic_settings-0.3.0-py3-none-any.whl (19.1 kB view details)

Uploaded Python 3

File details

Details for the file dj_dynamic_settings-0.3.0-py3-none-any.whl.

File metadata

File hashes

Hashes for dj_dynamic_settings-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 4f13cf96cea774f83705fecc0c578b33911b2ccf52cdc3bf0968491be0838c33
MD5 cc41d5826b917f9215a7eb9f04ca72d3
BLAKE2b-256 ababf2ee39a9d61bdf489061c85b31e911e9b79154a3cbb19b5f18b521f470d9

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page