Build Status Documentation Status Pypi Status

django-anysign

django-anysign is a Django application to manage online signature in a generic way.

Its goal is to provide a consistent API whatever the signature implementation, so that you can either:

  • switch from one signature backend to another;
  • use several backends at once.

See Alternatives & related projects for details about supported online signature services.

Project status

django-anysign is under active development. The project is not mature yet, but authors already use it! It means that, while API and implementation may change (improve!), authors do care of the changes.

Also, help is welcome! Feel free to report issues, request features or refactoring!

Contents

Install

django-anysign is open-source software, published under BSD license. See License for details.

Note

If you want to install a development environment, you should go to Contributing documentation.

Requirements

django-anysign has been tested against Python [1] 2.7, 3.3, 3.4 and 3.5. Other versions may work, but they are not part of the test suite at the moment.

Installing django-anysign will automatically trigger the installation of the following requirements:

    'Django>=1.8,<1.10',
    'setuptools',

As a library

In most cases, you will use django-anysign as a dependency of another project. In such a case, you should add django-anysign in your main project’s requirements. Typically in setup.py:

from setuptools import setup

setup(
    install_requires=[
        'django-anysign',
        #...
    ]
    # ...
)

Then when you install your main project with your favorite package manager (like pip [2]), django-anysign and its recursive dependencies will automatically be installed.

Standalone

You can install django-anysign with your favorite Python package manager. As an example with pip [2]:

pip install django-anysign

Check

Check django-anysign has been installed:

python -c "import django_anysign;print(django_anysign.__version__)"

You should get installed django_anysign‘s version.

References

[1]https://www.python.org/
[2](1, 2) https://pypi.python.org/pypi/pip/

Configure

Here is the list of settings used by django-anysign.

INSTALLED_APPS

There is no need to register django-anysign application in your Django’s INSTALLED_APPS setting.

ANYSIGN

The settings.ANYSIGN is a dictionary that contains all specific configuration for django-anysign.

Example from the Demo project:

ANYSIGN = {
    'BACKENDS': {
        'dummysign': 'django_dummysign.backend.DummySignBackend',
    },
    'SIGNATURE_TYPE_MODEL': 'django_anysign_demo.models.SignatureType',
    'SIGNATURE_MODEL': 'django_anysign_demo.models.Signature',
    'SIGNER_MODEL': 'django_anysign_demo.models.Signer',
}
BACKENDS

A dictionary where:

See also get_signature_backend().

SIGNATURE_TYPE_MODEL

The Python path to import the SignatureType model.

SIGNATURE_MODEL

The Python path to import the Signature model.

SIGNER_MODEL

The Python path to import the Signer model.

Models

django-anysign presumes digital signature involves models in the Django project: one to store the signatures, another to store signers, and one to store backend specific options.

That said, django-anysign does not embed concrete models: it provides base models you have to extend in your applications. This design allows you to customize models the way you like, i.e. depending on your use case.

Minimal integration

Here is the minimal integration you need in some models.py:

from django_anysign import api as django_anysign


class SignatureType(django_anysign.SignatureType):
    pass


class Signature(django_anysign.SignatureFactory(SignatureType)):
    pass


class Signer(django_anysign.SignerFactory(Signature)):
    pass

The example above is taken from django-anysign‘s Demo project.

SignatureType

class django_anysign.models.SignatureType(*args, **kwargs)

Bases: django.db.models.base.Model

Abstract base model for signature type.

A signature type encapsulates backend setup. Typically:

  • a “configured backend” is a backend class (such as DummySignBackend) and related configuration (URL, credentials...).
  • a Signature instance will be related to a configured backend, via a SignatureType.
signature_backend_code = None

Machine-readable code for the backend. Typically related to settings, by default keys in settings.ANYSIGN['BACKENDS'] dictionary.

class Meta
abstract = False
SignatureType.signature_backend_options

Dictionary for backend’s specific configuration.

Default implementation returns empty dictionary.

There are 2 main ways for you to setup backends with the right arguments:

  • in the model subclassing this one, override this property. This is the good option if you can have several SignatureType instances for one backend, i.e. if signature_backend_code is not unique.
  • in the backend’s subclass, make __init__() read the Django settings or environment. This can be a good option if you have an unique SignatureBackend instance matching a backend (signature_backend_code is unique).
SignatureType.get_signature_backend()

Instanciate and return signature backend instance.

Default implementation uses get_backend_instance() with signature_backend_code as positional arguement and with signature_backend_options() as keyword arguments.

SignatureType.signature_backend

Return backend from internal cache or new instance.

If signature_backend_code changed since the last access, then the internal (instance level) cache is invalidated and a new instance is returned.

Signature

django_anysign.models.SignatureFactory(SignatureType)

Return base class for signature model, using SignatureType model.

This pattern is the best one we found at the moment to have an abstract base model SignatureBase with appropriate foreign key to SignatureType model. Feel free to propose a better option if you know one ;)

Here is what you get in the Demo project:

class django_anysign_demo.models.Signature(id, signature_type, signature_backend_id, anysign_internal_id)

Bases: django_anysign.models.Signature

exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception Signature.MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

Signature.objects = <django.db.models.manager.Manager object>
Signature.signature_type

Accessor to the related object on the forward side of a many-to-one or one-to-one relation.

In the example:

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

child.parent is a ForwardManyToOneDescriptor instance.

Signature.signers

Accessor to the related objects manager on the reverse side of a many-to-one relation.

In the example:

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

parent.children is a ReverseManyToOneDescriptor instance.

Most of the implementation is delegated to a dynamically defined manager class built by create_forward_many_to_many_manager() defined below.

Signer

django_anysign.models.SignerFactory(Signature)

Return base class for signer model, using Signature model.

This pattern is the best one we found at the moment to have an abstract base model Signer with appropriate foreign key to Signature model. Feel free to propose a better option if you know one ;)

Here is what you get in the Demo project:

class django_anysign_demo.models.Signer(id, signature, signing_order, signature_backend_id, anysign_internal_id)

Bases: django_anysign.models.Signer

exception DoesNotExist

Bases: django.core.exceptions.ObjectDoesNotExist

exception Signer.MultipleObjectsReturned

Bases: django.core.exceptions.MultipleObjectsReturned

Signer.objects = <django.db.models.manager.Manager object>
Signer.signature

Accessor to the related object on the forward side of a many-to-one or one-to-one relation.

In the example:

class Child(Model):
    parent = ForeignKey(Parent, related_name='children')

child.parent is a ForwardManyToOneDescriptor instance.

Backends

django-anysign‘s signature backend encapsulates signature workflow and integration with vendor specific implementation.

Note

The backend API is quite experimental. This document deals with both vision (concepts) and current implementation (which may improve).

Scope of a backend

A signature backend is typically known by models and views. They use the backend to perform vendor-specific operations. The backend contains vendor-specific implementation that has to be shared with several consumers such as models and views.

A signature backend also typically knowns the workflows. So it should be helpful for URL resolution.

django-anysign’s SignatureBackend

Here is the current implementation of base backend.

class django_anysign.backend.SignatureBackend(name, code, url_namespace='anysign', **kwargs)

Bases: object

Encapsulate signature workflow and integration with vendor backend.

Here is a typical workflow:

  • SignatureType instance is created. It encapsulates the backend type and its configuration.
  • A Signature instance is created. The signature instance has a signature type attribute, hence a backend.
  • Signers are notified, by email, text or whatever. They get an hyperlink to the “signer view”. The URL may vary depending on the signature backend.
  • A signer goes to the backend’s “signer view” entry point: typically a view that integrates backend specific form to sign a document.
  • Most backends have a “notification view”, for the third-party service to signal updates.
  • Most backends have a “signer return view”, where the signer is redirected when he ends the signature process (whatever signature status).
  • The backend’s specific workflow can be made of several views. At the beginning, there is a Signature instance which carries data (typically a document). At the end, Signature is done.
name = None

Human-readable name.

code = None

Machine-readable name. Should be lowercase alphanumeric only, i.e. PEP-8 compliant.

url_namespace = None

Namespace for URL resolution.

send_signature(signature)

Initiate the signature process.

At this state, the signature object has been configured.

Typical implementation consists in sending signer URL to first signer.

Raise NotImplementedError if the backend does not support such a feature.

get_signer_url(signer)

Return URL where signer signs document.

Raise NotImplementedError in case the backend does not support “signer view” feature.

Default implementation reverses get_signer_url_name() with signer.pk as argument.

get_signer_url_name()

Return URL name where signer signs document.

Raise NotImplementedError in case the backend does not support “signer view” feature.

Default implementation returns anysign:signer.

get_signer_return_url(signer)

Return absolute URL where signer is redirected after signing.

The URL must be absolute because it is typically used by external signature service: the signer uses external web UI to sign the document(s) and then the signature service redirects the signer to (this) Django website.

Raise NotImplementedError in case the backend does not support “signer return view” feature.

Default implementation reverses get_signer_return_url_name() with signer.pk as argument.

get_signer_return_url_name()

Return URL name where signer is redirected once document has been signed.

Raise NotImplementedError in case the backend does not support “signer return view” feature.

Default implementation returns anysign:signer_return.

get_signature_callback_url(signature)

Return URL where backend can post signature notifications.

Raise NotImplementedError in case the backend does not support “signature callback url” feature.

Default implementation reverses get_signature_callback_url_name() with signature.pk as argument.

get_signature_callback_url_name()

Return URL name where backend can post signature notifications.

Raise NotImplementedError in case the backend does not support “signer return view” feature.

Default implementation returns anysign:signature_callback.

create_signature(signature)

Register signature in backend, return updated object.

This method is typically called by views which create Signature instances.

If backend stores a signature object, then implementation should update signature_backend_id.

Base implementation does nothing: override this method in backends.

django-dummysign’s SignatureBackend

Here is the demo signature backend implementation provided by django-dummysign.

import logging

from django_anysign import api as django_anysign


logger = logging.getLogger(__name__)


class DummySignBackend(django_anysign.SignatureBackend):
    def __init__(self):
        super(DummySignBackend, self).__init__(
            name='DummySign',
            code='dummysign',
        )

    def create_signature(self, signature):
        """Register ``signature`` in backend, return updated object.

        As a dummy backend: just emit a log.

        """
        signature = super(DummySignBackend, self).create_signature(signature)
        logger.debug('[django_dummysign] Signature created in backend')
        return signature

Views

At the moment, django-anysign does not provide views or generic views. But this feature is part of the Vision...

Loading

Since django-anysign does not provide concrete Models, and models are configured in settings, here are tools to load models and backends.

get_signature_backend

django_anysign.loading.get_signature_backend(code, *args, **kwargs)

Instantiate instance for backend_code with args and kwargs.

Get the backend factory (class) using settings.ANYSIGN['BACKENDS'].

Positional and keyword arguments are proxied as is.

get_signature_type_model

django_anysign.loading.get_signature_type_model()

Return model defined as settings.ANYSIGN['SIGNATURE_TYPE_MODEL'].

get_signature_model

django_anysign.loading.get_signature_model()

Return model defined as settings.ANYSIGN['SIGNATURE_MODEL'].

get_signer_model

django_anysign.loading.get_signer_model()

Return model defined as settings.ANYSIGN['SIGNER_MODEL'].

Demo project

Demo folder in project’s repository [1] contains a Django project to illustrate django-anysign usage. It basically integrates django-dummysign in a project.

Examples in the documentation are imported from the demo project.

Feel free to use the demo project as a sandbox. See Contributing for details about development environment setup.

Notes & references

[1]https://github.com/novafloss/django-anysign/tree/master/demo/

About django-anysign

This section is about the django-anysign project itself.

Vision

django-anysign provides conventions and base resources to implement digital signature features within a Django project.

django-anysign‘s goal is to provide a consistent API whatever the signature implementation. This concept basically covers the following use cases:

  • plug several signature backends and their specific workflows into a single website.
  • in a website using a single signature backend, migrate from one backend to another with minimum efforts.
  • as a developer, implement bindings for a new signature service vendor.

django-anysign presumes the following items are generally involved in digital signature features:

  • models. Such as signature, signer and signature type (backend options).
  • workflows. They usually start with the creation of a document to sign (setup a signature, assign signers, choose a backend). They usually end when the document has been signed by all signers. Steps between “start” and “end” typically vary depending on the vendor signature service.
  • views. Most signature workflows use similar views, such as “create signature”, “sign document”, “signer processed document” or “API callback”. Of course, the implementation and order vary depending on the vendor signature service. But some bits are generic.

django-anysign does not include vendor-specific implementation. Third-party projects do. And they can be based on django-anysign. So as a developer, you are likely to discover django-anysign via these vendor-specific projects. See Alternatives and related projects for details about third-party projects.

django-anysign is a framework. It does not provide all-in-one solutions. You may have to implement some things in your Django project. django-anysign tries to make this custom code easier to imagine and write, using conventions, utilities and base classes.

License

Copyright (c) 2014-2016, PeopleDoc. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  • Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  • Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
  • Neither the name of django-anysign nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Authors & contributors

Author: Benoît Bryon <benoit@marmelune.net>, as a former member of the PeopleDoc [1] team: https://github.com/peopledoc/

Maintainer: the PeopleDoc team <novafloss@people-doc.com>

Developers: https://github.com/novafloss/django-anysign/graphs/contributors

Notes & references

[1]http://www.people-doc.com

Changelog

This document describes changes between each past release. For information about future releases, check milestones [1] and Vision.

1.0 (2016-07-18)
  • Feature #21 - Drop support of Django 1.5, 1.6 and 1.7 Explicitly mark Django 1.5, 1.6 and 1.7 as not supported (tests fail with those versions) in packaging.
  • Add support of Django 1.9.
  • Breaking: django_anysign.api package is no longer imported in django_anysign root package, as it is not compatible with Django 1.9. Instead of doing import django_anysign, just do: from django_anysign import api as django_anysign.
  • Feature #24 - Use django UUIDField instead of uuidfield external app.
0.4 (2015-06-25)

Workaround identifiers and Django version.

  • Features #7 and #8 - Signature and Signer models have anysign_internal_id attribute. It is an unique identifier for signature or signer on Django side. For use as a “foreign key” in backend’s database, whenever possible. It defaults to an UUID. You may override it with a custom property if your models already have some UUID.
  • Feature #5 - Signer model has signature_backend_id attribute. Use it to store the backend’s signer identifier, i.e. signer’s identifier in external database.
  • Refactoring #15 - Project repository moved to github.com/novafloss (was github.com/novapost).
  • Refactoring #18 - Tox runs tests for multiple Django version.
  • Features #16 and #17 - Tests include Django version 1.7 and 1.8.
0.3 (2014-10-08)

Signers’ ordering.

  • Feature #4 - Added signing_order attribute to Signer model.
0.2 (2014-09-12)

Minor fixes.

  • Feature #2 - Explicitely mark Django 1.7 as not supported (tests fail with Django 1.7) in packaging.
  • Bug #3 - Fixed wrong usage of django-anysign API in django-dummysign backend.
0.1 (2014-08-11)

Initial release.

  • Introduced base model SignatureType and base model factories SignatureFactory and SignerFactory.
  • Introduced base backend class SignatureBackend.
  • Introduced loaders for custom models and backend: get_signature_backend_instance, get_signature_type_model, get_signature_model and get_signer_model.

Notes & references

[1]https://github.com/novafloss/django-anysign/milestones

Contributing

This document provides guidelines for people who want to contribute to the django-anysign project.

Create tickets

Please use django-anysign bugtracker [1] before starting some work:

  • check if the bug or feature request has already been filed. It may have been answered too!
  • else create a new ticket.
  • if you plan to contribute, tell us, so that we are given an opportunity to give feedback as soon as possible.
  • Then, in your commit messages, reference the ticket with some refs #TICKET-ID syntax.

Use topic branches

  • Work in branches.
  • Prefix your branch with the ticket ID corresponding to the issue. As an example, if you are working on ticket #23 which is about contribute documentation, name your branch like 23-contribute-doc.
  • If you work in a development branch and want to refresh it with changes from master, please rebase [2] or merge-based rebase [3], i.e. do not merge master.

Fork, clone

Clone django-anysign repository (adapt to use your own fork):

git clone git@github.com:novafloss/django-anysign.git
cd django-anysign/

Usual actions

The Makefile is the reference card for usual actions in development environment:

  • Install development toolkit with pip [4]: make develop.
  • Run tests with tox [5]: make test.
  • Build documentation: make documentation. It builds Sphinx [6] documentation in var/docs/html/index.html.
  • Release django-anysign project with zest.releaser [7]: make release.
  • Cleanup local repository: make clean, make distclean and make maintainer-clean.

See also make help.

Notes & references

[1]https://github.com/novafloss/django-anysign/issues
[2]https://git-scm.com/book/en/v2/Git-Branching-Rebasing
[3]http://tech.novapost.fr/psycho-rebasing-en.html
[4]https://pypi.python.org/pypi/pip/
[5]https://pypi.python.org/pypi/tox/
[6]https://pypi.python.org/pypi/Sphinx/
[7]https://pypi.python.org/pypi/zest.releaser/

Indices and tables