Package: django-activatable-model
Django Activatable ModelProvides functionality for Django models that have active and inactive states.
Features of this app are:An abstract BaseActivatableModel that allows the user to specify an
'activatable' (i.e. Boolean) fieldA model_activations_changed signal that fires when models' activatable field
are changed or bulk updatedValidation to ensure activatable models cannot be cascade deletedOverriding of delete methods so that the activatable flag is set to False
instead of the model(s) being deleted (unless force=True)Manager/QuerySet methods to activate and deactivate modelsInstallationpipinstalldjango-activatable-modelAddactivatable_modelto the list ofINSTALLED_APPS. Although this app does
not define any concrete models, it does connect signals that Django needs to
know about.Basic UsageAssume you have a model calledAccountin your app, and it is an activatable
model that has anamefield and aForeignKeyto aGroupmodel.fromactivatable_model.modelsimportBaseActivatableModelclassAccount(BaseActivatableModel):is_active=models.BooleanField(default=False)name=models.CharField(max_length=64)group=models.ForeignKey(Group)By just inheritingBaseActivatableModel, your model will need to define anis_activeboolean field (this field name can be changed, more on that later).
If you create anAccountmodel, themodel_activations_changedsignal will
be sent with anis_activekeyword argument set to False and aninstance_idskeyword argument that is a list of the single created account id. Similarly, if
you updated theis_activeflag at any time via the.save()method, themodel_activations_changedsignal will be emitted again. This allows the user
to do things like this:fromdjango.dispatchimportreceiverfromactivatable_modelimportmodel_activations_changed@receiver(model_activations_changed,sender=Account)defdo_something_on_deactivation(sender,instance_ids,is_active,**kwargs):ifnotis_active:# Either an account was deactivated or an inactive account was created...foraccountinAccount.objects.filter(id__in=instance_ids):# Do something with every deactivated accountActivatable Model DeletionDjango 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, theis_activeflag is set to False andmodel_activations_changedis fired.account=Account.objects.create(is_active=True)account.delete()# Or Account.objects.all().delete()# The account still existsprintAccount.objects.count()1# But it is deactivatedprintAccount.objects.get().is_activeFalseThe user can override this behavior by passingforce=Trueto the model or
queryset's.delete()method.Along with overriding deletion, Django activatable model also overrides cascade
deletion. No model that inheritsBaseActivatableModelcan be cascade deleted
by another model. This is accomplished by connecting to Django'spre_syncdbsignal and verifying that allForeignKeyandOneToOneFieldfields of
activatable models have theiron_deletearguments set to something other than
the default ofmodels.CASCADE.In fact, ourAccountmodel will not pass validation. In order to make it
validate properly on syncdb, it must do the following:fromdjango.dbimportmodelsclassAccount(BaseActivatableModel):is_active=models.BooleanField(default=False)name=models.CharField(max_length=64)group=models.ForeignKey(Group,on_delete=models.PROTECT)This will ensure aProtectedErroris thrown every time a Group is deleted.
For other options on foreign key deletion behavior, seeDjango's docs.Cascade Overrides (new in version 0.8.0 )As mentioned above, activatable models cannot be cascade deleted.  However,
this default behavior can be overridden by setting the the class variable,ALLOW_CASCADE_DELETE = True.  If set to True, than cascade deletion will
be allowed.  Note however, that this will be a hard delete, meaning that
cascade deletion will completely remove your record from the database rather
than applying the ActivatibleModel magic of simply marking it as inactive.Manager and QuerySet methodsDjango activatable models automatically use anActivatableManagermanager
that uses anActivatableQuerySetqueryset. This provides the following
functionality:Two methods -activate()anddeactivate()that can be applied to a
querysetOverriding theupdate()method so that it emitsmodel_activations_changedwhen theis_activeflag is updatedOverriding thedelete()method so that it callsdeactivate()unlessforce=TrueOverriding the activatable field nameThe name of the activatable field can be overridden by defining theACTIVATABLE_FIELD_NAMEconstant on the model to something else. By default,
this constant is set tois_active. An example is as follows:fromactivatable_modelimportBaseActivatableModelclassAccount(BaseActivatableModel):ACTIVATABLE_FIELD_NAME='active'active=models.BooleanField(default=False)In the above example, the model instructs the activatable model app to useactiveas the activatable field on the model. If the user does not define aBooleanFieldon the model with the same name asACTIVATABLE_FIELD_NAME, aValidationErroris raised during syncdb / migrate.Release Notes0.5.1Optimize individual saves so that they dont perform an additional query when checking if model activations have been updated0.5.0Changed 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 application0.4.2Fixed bug when activating a queryset that was filtered by the active flag0.3.1Added Django 1.7 app config0.3.0Added Django 1.7 support and backwards compatibility with Django 1.60.2.0When upgrading to this version, users will have to explicitly add theis_activefield to any models that inheritedBaseActivatableModel. This
field had a default value of False before, so be sure to add that as the
default for the boolean field.LicenseMIT License (see theLICENSEfile in this repo)