forkit-django

Utility functions for forking, resetting and diffing model objects


Keywords
fork, deepcopy, model, abstract, diff
License
BSD-3-Clause
Install
pip install forkit-django==0.10.1

Documentation

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 of deep.
  • exclude - A list of fields to not fork (not applicable if fields is defined)
  • deep - If True, traversing all related objects and creates forks of them as well, effectively creating a new tree of objects.
  • commit - If True, all forks (including related objects) will be saved in the order of dependency. If False, 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 - a dict of the keyword arguments passed into forkit.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 - a dict of the keyword arguments passed into forkit.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 - a dict of the keyword arguments passed into forkit.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 the reference and instance

Contributing

To run the tests locally:

  1. Clone the project

  2. Create a test database:

    psql -c "CREATE DATABASE forkit;"

  3. Create a virtual environment. (Optional, but recommended.)

  4. Install tox.

  5. 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.