Package: django-fsm-freeze
django fsm data immutability supportdjango-fsm-freeze provides a django model mixin for data immutability based ondjango-fsm.Installationpip install django-fsm-freezeConfigurationBasic configurationAddFreezableFSMModelMixinto yourdjango-fsmmodelSpecify theFROZEN_IN_STATESin which the object should be frozen, meaning the
value of its fields/attributes cannot be changed.(optional) Customize theNON_FROZEN_FIELDSfor partial mutabilityWhen an object is in a frozen state, by default all of its fields are immutable,
except for thestateFSMField which needs to be mutable fordjango-fsmto work.fromdjango_fsmimportFSMFieldfromdjango_fsm_freeze.modelsimportFreezableFSMModelMixinclassMyDjangoFSMModel(FreezableFSMModelMixin):# In this example, when object is in the 'active' state, it is immutable.FROZEN_IN_STATES=('active',)# django-fsm specifics: state, transitions, etc.state=FSMField(default='new')# ...CustomizationTell django-fsm-freeze which field to look up for frozenessBy default, FreezableFSMModelMixin will look for the FSMField on your model
and its value to determine whether the instance is frozen or not.
However, in case your model has multipleFSMFields,
you would need to tell the Mixin which field should be used to look up,
to determine the frozeness via theFROZEN_STATE_LOOKUP_FIELDattribute.fromdjango.dbimportmodelsfromdjango_fsmimportFSMFieldfromdjango_fsm_freeze.modelsimportFreezableFSMModelMixinclassMyDjangoFSMModel(FreezableFSMModelMixin):# In this example, when object is in the 'active' state, it is immutable.FROZEN_IN_STATES=('active',)# Assign this with the name of the `FSMField` if your models has multiple FSMFields.# See example in `mytest/models.py:FakeModel2`FROZEN_STATE_LOOKUP_FIELD='state'# django-fsm specifics: state, transitions, etc.state=FSMField(default='new')another_state=FSMField(default='draft')# ...In another case, when the desired lookup state is on another model related
via foreign key, instead of settingFROZEN_STATE_LOOKUP_FIELD,
it is possible to specify the (dot-separated) path to that field inFROZEN_DELEGATE_TO.
This setting instructs the freezable model instance to evaluate the freezable
state from that remote field.classParent(FreezableFSMModelMixin):state=FSMField(default='new')classChild(FreezableFSMModelMixin):# Assign this with the path (dotted separated) to the instance you expect# the decision for freezability to be decided on.FROZEN_DELEGATE_TO='parent'parent=models.ForeignKey(Parent,on_delete=models.PROTECT)Define for partial mutabilityIn case we want to mutate certain fields when the object is frozen, we can
set theNON_FROZEN_FIELDSto allow it.classMyDjangoFSMModel(FreezableFSMModelMixin):# In this example, when object is in the 'active' state, it is immutable.FROZEN_IN_STATES=('active',)NON_FROZEN_FIELDS=('a_mutable_field',)# This field is mutable even when the object is in the frozen state.a_mutable_field=models.BooleanField()See configuration example inhttps://github.com/ming-tung/django-fsm-freeze/blob/main/mytest/models.pyUsageThe frozen check takes place whenclass is prepared (configuration checking)object.save()object.delete()In case of trying to save/delete a frozen object, aFreezeValidationErrorwill be raised.
In case of misconfiguration, aFreezeConfigurationErrorwill be raised.BypassingIf you want to bypass the frozen check for some reason, you can use the contextmanagerbypass_fsm_freeze(), with the freezable object(s) that you want to bypass
the checks on, or apply the bypass globally viabypass_globallyargument.You can find some usage example in testmytest/test_models.py:TestBypassFreezeCheck.DevelopingFor contributors or developers of the project, please seeDEVELOPING.mdContributing(TODO)
For anyone who is interested in contributing to this project, please seeCONTRIBUTING.md.
Thank you :)For further discussions or suggestions, you could also reach out to me on twitter or email.