================
django-read-only
================
.. image:: https://img.shields.io/github/actions/workflow/status/adamchainz/django-read-only/main.yml?branch=main&style=for-the-badge
:target: https://github.com/adamchainz/django-read-only/actions?workflow=CI
.. image:: https://img.shields.io/badge/Coverage-100%25-success?style=for-the-badge
:target: https://github.com/adamchainz/django-read-only/actions?workflow=CI
.. image:: https://img.shields.io/pypi/v/django-read-only.svg?style=for-the-badge
:target: https://pypi.org/project/django-read-only/
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg?style=for-the-badge
:target: https://github.com/psf/black
.. image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=for-the-badge
:target: https://github.com/pre-commit/pre-commit
:alt: pre-commit
Disable Django database writes.
Requirements
------------
Python 3.8 to 3.12 supported.
Django 3.2 to 5.0 supported.
----
**Want to work smarter and faster?**
Check out my book `Boost Your Django DX <https://adamchainz.gumroad.com/l/byddx>`__ which covers django-read-only, IPython, and many other tools.
----
Installation
------------
Install with **pip**:
.. code-block:: sh
python -m pip install django-read-only
Then add to your installed apps:
.. code-block:: python
INSTALLED_APPS = [
...,
"django_read_only",
...,
]
Usage
-----
In your settings file, set ``DJANGO_READ_ONLY`` to ``True`` and all data modification queries will cause an exception:
.. code-block:: console
$ DJANGO_READ_ONLY=1 python manage.py shell
...
>>> User.objects.create_user(username="hacker", password="hunter2")
...
DjangoReadOnlyError(...)
For convenience, you can also control this with the ``DJANGO_READ_ONLY`` environment variable, which will count as ``True`` if set to anything but the empty string.
The setting takes precedence over the environment variable.
During a session with ``DJANGO_READ_ONLY`` set on, you can re-enable writes by calling ``enable_writes()``:
.. code-block:: pycon
>>> import django_read_only
>>> django_read_only.enable_writes()
Writes can be disabled with ``disable_writes()``:
.. code-block:: pycon
>>> django_read_only.disable_writes()
To temporarily allow writes, use the ``temp_writes()`` context manager / decorator:
.. code-block:: pycon
>>> with django_read_only.temp_writes():
... User.objects.create_user(...)
...
Note that writes being enabled/disabled is global state, affecting all threads and asynchronous coroutines.
Recommended Setup
-----------------
Set read-only mode on in your production environment, and maybe staging, during interactive sessions.
This can be done by setting the ``DJANGO_READ_ONLY`` environment variable in the shell profile file (``bashrc``, ``zshrc``, etc.) of the system’s user account.
This way developers performing exploratory queries can’t accidentally make changes, but writes will remain enabled for non-shell processes like your WSGI server.
With this setup, developers can also run management commands with writes enabled by setting the environment variable before the command:
.. code-block:: console
$ DJANGO_READ_ONLY= python manage.py clearsessions
Some deployment platforms don’t allow you to customize your shell profile files.
In this case, you will need to find a way to detect shell mode from within your settings file.
For example, on Heroku there’s the ``DYNO`` environment variable (`docs <https://devcenter.heroku.com/articles/dynos#local-environment-variables>`__) to identify the current virtual machine.
It starts with “run.” for interactive sessions.
You can use this to enable read-only mode in your settings file like so:
.. code-block:: python
if os.environ.get("DYNO", "").startswith("run."):
DJANGO_READ_ONLY = bool(os.environ.get("DJANGO_READ_ONLY", "1"))
else:
DJANGO_READ_ONLY = False
IPython Extension
-----------------
django-read-only also works as an IPython extension for quick access to enable/disable read-only mode.
Load it with:
.. code-block:: ipython
In [1]: %load_ext django_read_only
You can have the extension always load by setting it up to your `IPython configuration file <https://ipython.readthedocs.io/en/stable/config/intro.html>`__:
.. code-block:: python
c.InteractiveShellApp.extensions.append("django_read_only")
When loaded, use the ``%read_only`` line magic to disable or enable read-only mode:
.. code-block:: ipython
In [2]: %read_only off
Write queries enabled.
In [3]: %read_only on
Write queries disabled.
This reduces the amount of typing needed to disable read-only mode.
How it Works
------------
The most accurate way to prevent writes is to connect as a separate database user with only read permission.
However, this has limitations - Django doesn’t support modifying the ``DATABASES`` setting live, so sessions would not be able to temporarily allow writes.
Instead, django-read-only uses `always installed database instrumentation <https://adamj.eu/tech/2020/07/23/how-to-make-always-installed-django-database-instrumentation/>`__ to inspect executed queries and only allow those which look like reads.
It uses a “fail closed” philosophy, so anything unknown will fail, which should be fairly reasonable.
Because django-read-only uses Django database instrumentation, it cannot block queries running through the underlying database connection (accesses through ``django.db.connection.connection``), and it cannot filter operations within stored procedures (which use ``connection.callproc()``).
These are very rare in practice though, so django-read-only’s method works well for most projects.
Raw data
{
"_id": null,
"home_page": "https://github.com/adamchainz/django-read-only",
"name": "django-read-only",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": "",
"keywords": "Django",
"author": "Adam Johnson",
"author_email": "me@adamj.eu",
"download_url": "https://files.pythonhosted.org/packages/03/1f/d5569c1b6eda6c610ac2b90a57d6e86410651c2b79ad6af5f94cefbaf799/django_read_only-1.15.0.tar.gz",
"platform": null,
"description": "================\ndjango-read-only\n================\n\n.. image:: https://img.shields.io/github/actions/workflow/status/adamchainz/django-read-only/main.yml?branch=main&style=for-the-badge\n :target: https://github.com/adamchainz/django-read-only/actions?workflow=CI\n\n.. image:: https://img.shields.io/badge/Coverage-100%25-success?style=for-the-badge\n :target: https://github.com/adamchainz/django-read-only/actions?workflow=CI\n\n.. image:: https://img.shields.io/pypi/v/django-read-only.svg?style=for-the-badge\n :target: https://pypi.org/project/django-read-only/\n\n.. image:: https://img.shields.io/badge/code%20style-black-000000.svg?style=for-the-badge\n :target: https://github.com/psf/black\n\n.. image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=for-the-badge\n :target: https://github.com/pre-commit/pre-commit\n :alt: pre-commit\n\nDisable Django database writes.\n\nRequirements\n------------\n\nPython 3.8 to 3.12 supported.\n\nDjango 3.2 to 5.0 supported.\n\n----\n\n**Want to work smarter and faster?**\nCheck out my book `Boost Your Django DX <https://adamchainz.gumroad.com/l/byddx>`__ which covers django-read-only, IPython, and many other tools.\n\n----\n\nInstallation\n------------\n\nInstall with **pip**:\n\n.. code-block:: sh\n\n python -m pip install django-read-only\n\nThen add to your installed apps:\n\n.. code-block:: python\n\n INSTALLED_APPS = [\n ...,\n \"django_read_only\",\n ...,\n ]\n\nUsage\n-----\n\nIn your settings file, set ``DJANGO_READ_ONLY`` to ``True`` and all data modification queries will cause an exception:\n\n.. code-block:: console\n\n $ DJANGO_READ_ONLY=1 python manage.py shell\n ...\n >>> User.objects.create_user(username=\"hacker\", password=\"hunter2\")\n ...\n DjangoReadOnlyError(...)\n\nFor convenience, you can also control this with the ``DJANGO_READ_ONLY`` environment variable, which will count as ``True`` if set to anything but the empty string.\nThe setting takes precedence over the environment variable.\n\nDuring a session with ``DJANGO_READ_ONLY`` set on, you can re-enable writes by calling ``enable_writes()``:\n\n.. code-block:: pycon\n\n >>> import django_read_only\n >>> django_read_only.enable_writes()\n\nWrites can be disabled with ``disable_writes()``:\n\n.. code-block:: pycon\n\n >>> django_read_only.disable_writes()\n\nTo temporarily allow writes, use the ``temp_writes()`` context manager / decorator:\n\n.. code-block:: pycon\n\n >>> with django_read_only.temp_writes():\n ... User.objects.create_user(...)\n ...\n\nNote that writes being enabled/disabled is global state, affecting all threads and asynchronous coroutines.\n\nRecommended Setup\n-----------------\n\nSet read-only mode on in your production environment, and maybe staging, during interactive sessions.\nThis can be done by setting the ``DJANGO_READ_ONLY`` environment variable in the shell profile file (``bashrc``, ``zshrc``, etc.) of the system\u2019s user account.\nThis way developers performing exploratory queries can\u2019t accidentally make changes, but writes will remain enabled for non-shell processes like your WSGI server.\n\nWith this setup, developers can also run management commands with writes enabled by setting the environment variable before the command:\n\n.. code-block:: console\n\n $ DJANGO_READ_ONLY= python manage.py clearsessions\n\nSome deployment platforms don\u2019t allow you to customize your shell profile files.\nIn this case, you will need to find a way to detect shell mode from within your settings file.\n\nFor example, on Heroku there\u2019s the ``DYNO`` environment variable (`docs <https://devcenter.heroku.com/articles/dynos#local-environment-variables>`__) to identify the current virtual machine.\nIt starts with \u201crun.\u201d for interactive sessions.\nYou can use this to enable read-only mode in your settings file like so:\n\n.. code-block:: python\n\n if os.environ.get(\"DYNO\", \"\").startswith(\"run.\"):\n DJANGO_READ_ONLY = bool(os.environ.get(\"DJANGO_READ_ONLY\", \"1\"))\n else:\n DJANGO_READ_ONLY = False\n\nIPython Extension\n-----------------\n\ndjango-read-only also works as an IPython extension for quick access to enable/disable read-only mode.\nLoad it with:\n\n.. code-block:: ipython\n\n In [1]: %load_ext django_read_only\n\nYou can have the extension always load by setting it up to your `IPython configuration file <https://ipython.readthedocs.io/en/stable/config/intro.html>`__:\n\n.. code-block:: python\n\n c.InteractiveShellApp.extensions.append(\"django_read_only\")\n\nWhen loaded, use the ``%read_only`` line magic to disable or enable read-only mode:\n\n.. code-block:: ipython\n\n In [2]: %read_only off\n Write queries enabled.\n\n In [3]: %read_only on\n Write queries disabled.\n\nThis reduces the amount of typing needed to disable read-only mode.\n\nHow it Works\n------------\n\nThe most accurate way to prevent writes is to connect as a separate database user with only read permission.\nHowever, this has limitations - Django doesn\u2019t support modifying the ``DATABASES`` setting live, so sessions would not be able to temporarily allow writes.\n\nInstead, django-read-only uses `always installed database instrumentation <https://adamj.eu/tech/2020/07/23/how-to-make-always-installed-django-database-instrumentation/>`__ to inspect executed queries and only allow those which look like reads.\nIt uses a \u201cfail closed\u201d philosophy, so anything unknown will fail, which should be fairly reasonable.\n\nBecause django-read-only uses Django database instrumentation, it cannot block queries running through the underlying database connection (accesses through ``django.db.connection.connection``), and it cannot filter operations within stored procedures (which use ``connection.callproc()``).\nThese are very rare in practice though, so django-read-only\u2019s method works well for most projects.\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Disable Django database writes.",
"version": "1.15.0",
"project_urls": {
"Changelog": "https://github.com/adamchainz/django-read-only/blob/main/CHANGELOG.rst",
"Homepage": "https://github.com/adamchainz/django-read-only",
"Mastodon": "https://fosstodon.org/@adamchainz",
"Twitter": "https://twitter.com/adamchainz"
},
"split_keywords": [
"django"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "e9bb74403cb11a8df2cdff5065ed738619eb0ed779cc2cb58ea53fdfdd04fcfc",
"md5": "1812ac78cf85740a78cf37cfddb326cf",
"sha256": "42b9fedfcebc2d44d7cf1f5e8a525580f21e4e539d821f4eca87629258316d0e"
},
"downloads": -1,
"filename": "django_read_only-1.15.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "1812ac78cf85740a78cf37cfddb326cf",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 6561,
"upload_time": "2023-10-11T09:36:37",
"upload_time_iso_8601": "2023-10-11T09:36:37.190198Z",
"url": "https://files.pythonhosted.org/packages/e9/bb/74403cb11a8df2cdff5065ed738619eb0ed779cc2cb58ea53fdfdd04fcfc/django_read_only-1.15.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "031fd5569c1b6eda6c610ac2b90a57d6e86410651c2b79ad6af5f94cefbaf799",
"md5": "62eac6f19d344bf208aa0ec11327c0cc",
"sha256": "1628f65e13ca7f692e0bfc0df8cefda96a8179b8ae27a03175be318d7dac2c90"
},
"downloads": -1,
"filename": "django_read_only-1.15.0.tar.gz",
"has_sig": false,
"md5_digest": "62eac6f19d344bf208aa0ec11327c0cc",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 7736,
"upload_time": "2023-10-11T09:36:38",
"upload_time_iso_8601": "2023-10-11T09:36:38.434950Z",
"url": "https://files.pythonhosted.org/packages/03/1f/d5569c1b6eda6c610ac2b90a57d6e86410651c2b79ad6af5f94cefbaf799/django_read_only-1.15.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-10-11 09:36:38",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "adamchainz",
"github_project": "django-read-only",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"tox": true,
"lcname": "django-read-only"
}