Overview
Forkit-Django is composed of a set of utility functions for forking, resetting, and diffing model objects.
Note: This project is a fork of Django-Forkit, which is now unmaintained.
Below are a list of the current utility functions:
forkit.tools.fork
Creates and returns a new object that is identical to reference
.
-
fields
- A list of fields to fork. If a falsy value, the fields will be inferred depending on the value ofdeep
. -
exclude
- A list of fields to not fork (not applicable iffields
is defined) -
deep
- IfTrue
, traversing all related objects and creates forks of them as well, effectively creating a new tree of objects. -
commit
- IfTrue
, all forks (including related objects) will be saved in the order of dependency. IfFalse
, all commits are stashed away until the root fork is committed. -
**kwargs
- Any additional keyword arguments are passed along to all signal receivers. Useful for altering runtime behavior in signal receivers.
fork(reference, [fields=None], [exclude=('pk',)], [deep=False], [commit=True], [**kwargs])
forkit.tools.reset
Same parameters as above, except that an explicit instance
is rquired and
will result in an in-place update of instance
. For shallow resets, only the
local non-relational fields will be updated. For deep resets, direct
foreign keys will be traversed and reset. Many-to-many and reverse foreign keys
are not attempted to be reset because the comparison between the related objects
for reference
and the related objects for instance
becomes ambiguous.
reset(reference, instance, [fields=None], [exclude=('pk',)], [deep=False], [commit=True], [**kwargs])
forkit.tools.commit
Commits any unsaved changes to a forked or reset object.
commit(reference, [**kwargs])
forkit.tools.diff
Performs a diff between two model objects of the same type. The output is a
dict
of differing values relative to reference
. Thus, if
reference.foo
is bar
and instance.foo
is baz
, the output will
be {'foo': 'baz'}
. Note: deep diffs only work for simple non-circular
relationships. Improved functionality is scheduled for a future release.
diff(reference, instance, [fields=None], [exclude=('pk',)], [deep=False], [**kwargs])
ForkableModel
Also included is a Model
subclass which has implements the above functions
as methods.
from forkit.models import ForkableModel
class Author(ForkableModel):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
Let's create starting object:
author = Author(first_name='Byron', last_name='Ruth')
author.save()
To create copy, simply call the fork
method.
author_fork = author.fork()
When an object is forked, it immediately inherits it's data including related objects.
author_fork.first_name # Byron
author_fork.last_name # Ruth
Let us change something on the fork and use the diff
method to compare it
against the original author
. It returns a dictionary of the differences
between itself and the passed in object.
author_fork.first_name = 'Edward'
author_fork.diff(author) # {'first_name': 'Edward'}
Once satisfied with the changes, simply call commit
.
author_fork.commit()
Signals
For each of the utility function above, pre_FOO
and post_FOO
signals
are sent allowing for a decoupled approached for customizing behavior, especially
when performing deep operations.
forkit.signals.pre_fork
-
sender
- the model class of the instance -
reference
- the reference object the fork is being created from -
instance
- the forked object itself -
config
- adict
of the keyword arguments passed intoforkit.tools.fork
forkit.signals.post_fork
-
sender
- the model class of the instance -
reference
- the reference object the fork is being created from -
instance
- the forked object itself
forkit.signals.pre_reset
-
sender
- the model class of the instance -
reference
- the reference object the instance is being reset relative to -
instance
- the object being reset -
config
- adict
of the keyword arguments passed intoforkit.tools.reset
forkit.signals.post_reset
-
sender
- the model class of the instance -
reference
- the reference object the instance is being reset relative to -
instance
- the object being reset
forkit.signals.pre_commit
-
sender
- the model class of the instance -
reference
- the reference object the instance has been derived -
instance
- the object to be committed
forkit.signals.post_commit
-
sender
- the model class of the instance -
reference
- the reference object the instance has been derived -
instance
- the object that has been committed
forkit.signals.pre_diff
-
sender
- the model class of the instance -
reference
- the reference object the instance is being diffed against -
instance
- the object being diffed with -
config
- adict
of the keyword arguments passed intoforkit.tools.diff
forkit.signals.post_diff
-
sender
- the model class of the instance -
reference
- the reference object the instance is being diffed against -
instance
- the object being diffed with -
diff
- the diff between thereference
andinstance
Contributing
To run the tests locally:
-
Clone the project
-
Create a test database:
psql -c "CREATE DATABASE forkit;"
-
Create a virtual environment. (Optional, but recommended.)
-
Install tox.
-
Run
tox
.
If you want to change the database from Postgres to something else, you can
define DATABASE_URL
as allowed by django-environ.
When using tox, you can pass arguments to the test runner like so:
tox -- --reverse --verbosity=1 forkit.tests.test_utils
Once the tests have run, a detailed breakdown of the test coverage should be
available in the htmlcov/
folder.