django-db-readonly 0.7.0
Add a global database read-only setting.
License: MIT
Keywords: django database readonly

# django-db-readonly

## About

A way to globally disable writes to your database. This works by
inserting a cursor wrapper between Django's `CursorWrapper` and the
database connection's cursor wrapper.

## Installation

Install with:

```sh
> pip install django-db-readonly
```

Then add `readonly` to your `INSTALLED_APPS`.

```python
INSTALLED_APPS = (
    # ...
    'readonly',
    # ...
)
```

## Usage

You need to add this line to your `settings.py` to make the database read-only:

```python
# Set to False to allow writes
SITE_READ_ONLY = True
```

When you do this, any write action to your databases will generate an exception. You should catch this exception and deal with it somehow. Or let Django display an [error 500
page](http://docs.djangoproject.com/en/1.3/topics/http/urls/#handler500). The exception you will want to catch is
`readonly.exceptions.DatabaseWriteDenied` which inherits from
`django.db.utils.DatabaseError`.

There is also a middleware class that will handle the exceptions and attempt to handle them as explained below. To enable the middleware, add the following line to your `settings.py`:

```python
MIDDLEWARE = (
    # ...
    'readonly.middleware.DatabaseReadOnlyMiddleware',
    # ...
)
```

This will then catch `DatabaseWriteDenied` exceptions. If the request is a POST request, we will redirect the user to the same URL, but as a GET request. If the request is not a POST (ie. a GET), we will just display a `HttpResponse` with text telling the user the site is in read-only mode.

In addition, the middleware class can add an error-type message using the `django.contrib.messages` module. Add:

```python
# Enable
DB_READ_ONLY_MIDDLEWARE_MESSAGE = True
```

to your `settings.py` and then on POST requests that generate a `DatabaseWriteDenied` exception, we will add an error message informing the user that the site is in read-only mode.

For additional messaging, there is a context processor that adds `SITE_READ_ONLY` into the context. Add the following line in your `settings.py`:

```python
TEMPLATE_CONTEXT_PROCESSORS = (
    # ...
    'readonly.context_processors.readonly',
    # ...
)
```

And use it as you would any boolean in the template, e.g. `{% if SITE_READ_ONLY %}We're down for maintenance.{% endif %}`

## Configuration

- `SITE_READ_ONLY` - Use to disable writes to the database.
- `DB_READ_ONLY_DATABASES` - A list of database names that read only is enforced on (and ignored for others).
- `DB_READ_ONLY_MIDDLEWARE_MESSAGE` - A custom message that can be used to tell the user when the DB is in readonly mode.

## Testing

Tests are pretty basic, right now.

## Caveats

This will work with [Django Debug Toolbar](https://github.com/robhudson/django-debug-toolbar). In fact, I was inspired by [DjDT's sql panel](https://github.com/robhudson/django-debug-toolbar/blob/master/debug_toolbar/panels/sql.py) when writing this app.

However, in order for both DDT _and_ django-db-readonly to work, you need to make sure that you have `readonly` before `debug_toolbar` in your `INSTALLED_APPS`. Otherwise, you are responsible for debugging what is going on. Of course, I'm not sure why you'd be running DDT in production and running django-db-readonly in development, but whatever, I'm not you.

More generally, if you have any other apps that modifies either `django.db.backends.util.CursorWrapper` or `django.db.backends.util.CursorDebugWrapper`, you need to make sure that `readonly` is placed _before_ of those apps in `INSTALLED_APPS`.

## The Nitty Gritty

How does this do what it does? Well, django-db-readonly sits between Django's own cursor wrapper at `django.db.backends.util.CursorWrapper` and the database specific cursor at `django.db.backends.*.base.*CursorWrapper`. It overrides two specific methods: `execute` and `executemany`. If the site is in read-only mode, then the SQL is examined to see if it contains any write actions (defined in `readonly.ReadOnlyCursorWrapper.SQL_WRITE_BLACKLIST`). If a write is detected, an exception is raised.

[![CircleCI](https://circleci.com/gh/streeter/django-db-readonly.svg?style=svg)](https://circleci.com/gh/streeter/django-db-readonly) [![PyPI version](https://badge.fury.io/py/django-db-readonly.svg)](https://badge.fury.io/py/django-db-readonly)

## Copyright

Copyright © 2020, Chris Streeter under the MIT software license. See LICENSE for more information.