Skip to main content

Django Activatable Model

Project description

[![Build Status](https://travis-ci.org/ambitioninc/django-activatable-model.png)](https://travis-ci.org/ambitioninc/django-activatable-model)
# Django Activatable Model

Provides functionality for Django models that have active and inactive states.
Features of this app are:

1. An abstract BaseActivatableModel that allows the user to specify an
'activatable' (i.e. Boolean) field
1. A model_activations_changed signal that fires when models' activatable field
are changed or bulk updated
1. Validation to ensure activatable models cannot be cascade deleted
1. Overriding of delete methods so that the activatable flag is set to False
instead of the model(s) being deleted (unless force=True)
1. Manager/QuerySet methods to activate and deactivate models

## Installation
```bash
pip install django-activatable-model
```

Add `activatable_model` to the list of `INSTALLED_APPS`. Although this app does
not define any concrete models, it does connect signals that Django needs to
know about.

## Basic Usage
Assume you have a model called `Account` in your app, and it is an activatable
model that has a `name` field and a `ForeignKey` to a `Group` model.

```python
from activatable_model import BaseActivatableModel

class Account(BaseActivatableModel):
is_active = models.BooleanField(default=False)
name = models.CharField(max_length=64)
group = models.ForeignKey(Group)
```

By just inheriting `BaseActivatableModel`, your model will need to define an
`is_active` boolean field (this field name can be changed, more on that later).
If you create an `Account` model, the `model_activations_changed` signal will
be sent with an `is_active` keyword argument set to False and an `instance_ids`
keyword argument that is a list of the single created account id. Similarly, if
you updated the `is_active` flag at any time via the `.save()` method, the
`model_activations_changed` signal will be emitted again. This allows the user
to do things like this:

```python
from django.dispatch import receiver
from activatable_model import model_activations_changed

@receiver(model_activations_changed, sender=Account)
def do_something_on_deactivation(sender, instance_ids, is_active, **kwargs):
if not is_active:
# Either an account was deactivated or an inactive account was created...
for account in Account.objects.filter(id__in=instance_ids):
# Do something with every deactivated account
```

## Activatable Model Deletion
Django activatable model is meant for models that should never be deleted but
rather activated/deactivated instead. Given the assumption that activatable
models should never be deleted, Django activatable model does some magic
underneath to ensure your activatable models are properly updated when the user
calls `.delete()`. Instead of deleting the object(s) directly, the `is_active`
flag is set to False and `model_activations_changed` is fired.

```python
account = Account.objects.create(is_active=True)
account.delete() # Or Account.objects.all().delete()

# The account still exists
print Account.objects.count()
1

# But it is deactivated
print Account.objects.get().is_active
False
```

The user can override this behavior by passing `force=True` to the model or
queryset's `.delete()` method.

Along with overriding deletion, Django activatable model also overrides cascade
deletion. No model that inherits `BaseActivatableModel` can be cascade deleted
by another model. This is accomplished by connecting to Django's `pre_syncdb`
signal and verifying that all `ForeignKey` and `OneToOneField` fields of
activatable models have their `on_delete` arguments set to something other than
the default of `models.CASCADE`.

In fact, our `Account` model will not pass validation. In order to make it
validate properly on syncdb, it must do the following:

```python
from django.db import models

class Account(BaseActivatableModel):
is_active = models.BooleanField(default=False)
name = models.CharField(max_length=64)
group = models.ForeignKey(Group, on_delete=models.PROTECT)
```

This will ensure a `ProtectedError` is thrown every time a Group is deleted.
For other options on foreign key deletion behavior, see
[Django's docs](https://docs.djangoproject.com/en/1.7/ref/models/fields/#django.db.models.ForeignKey.on_delete).

## Manager and QuerySet methods
Django activatable models automatically use an `ActivatableManager` manager
that uses an `ActivatableQuerySet` queryset. This provides the following
functionality:

1. Two methods - `activate()` and `deactivate()` that can be applied to a
queryset
1. Overriding the `update()` method so that it emits
`model_activations_changed` when the `is_active` flag is updated
1. Overriding the `delete()` method so that it calls `deactivate()` unless
`force=True`

## Overriding the activatable field name
The name of the activatable field can be overridden by defining the
`ACTIVATABLE_FIELD_NAME` constant on the model to something else. By default,
this constant is set to `is_active`. An example is as follows:

```python
from activatable_model import BaseActivatableModel

class Account(BaseActivatableModel):
ACTIVATABLE_FIELD_NAME = 'active'
active = models.BooleanField(default=False)
```

In the above example, the model instructs the activatable model app to use
`active` as the activatable field on the model. If the user does not define a
`BooleanField` on the model with the same name as `ACTIVATABLE_FIELD_NAME`, a
`ValidationError` is raised during syncdb / migrate.

## Release Notes
* 0.5.0
* Changed the signal to send instance_ids as a keyword argument rather than the instances. This pushes fetching the updated models in signal handlers onto the application
* 0.4.2
* Fixed bug when activating a queryset that was filtered by the active flag
* 0.3.1
* Added Django 1.7 app config
* 0.3.0
* Added Django 1.7 support and backwards compatibility with Django 1.6

* 0.2.0
* When upgrading to this version, users will have to explicitly add the
`is_active` field to any models that inherited `BaseActivatableModel`. This
field had a default value of False before, so be sure to add that as the
default for the boolean field.

## License
MIT License (see the [LICENSE](LICENSE) file in this repo)

Project details


Download files

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

Source Distribution

django-activatable-model-0.5.0.tar.gz (9.3 kB view details)

Uploaded Source

Built Distribution

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

django_activatable_model-0.5.0-py2.py3-none-any.whl (14.0 kB view details)

Uploaded Python 2Python 3

File details

Details for the file django-activatable-model-0.5.0.tar.gz.

File metadata

File hashes

Hashes for django-activatable-model-0.5.0.tar.gz
Algorithm Hash digest
SHA256 856f6b18daf8ddcdfa412e6b93a651eb3c1d2d0ea3ca32bdb61f29b65a5c1f16
MD5 8c343a33f79a925f2c0f9a8167de7462
BLAKE2b-256 1d79ee2affac99c9bd233ab01e02061c9758509cd6933b73ae61fbb57e9ddc57

See more details on using hashes here.

File details

Details for the file django_activatable_model-0.5.0-py2.py3-none-any.whl.

File metadata

File hashes

Hashes for django_activatable_model-0.5.0-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 c327e4f05348dec0ffc235c9b086a84db1e22ec21754bb9adf05720381520d9b
MD5 ab889dd2000188b7b1995cfb30f67257
BLAKE2b-256 ec25903ea71d415eb6614475e6047ef022b0510330bfa97bd4ab580a4a20db88

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