API

Forms

ConcurrentForm

class concurrency.forms.ConcurrentForm(data=None, files=None, auto_id=u'id_%s', prefix=None, initial=None, error_class=<class 'django.forms.utils.ErrorList'>, label_suffix=None, empty_permitted=False, instance=None, use_required_attribute=None)[source]

Simple wrapper to ModelForm that try to mitigate some concurrency error. Note that is always possible have a RecordModifiedError in model.save(). Statistically form.clean() should catch most of the concurrent editing, but is good to catch RecordModifiedError in the view too.

VersionWidget

class concurrency.forms.VersionWidget(attrs=None)[source]

Widget that show the revision number using <div>

Usually VersionField use HiddenInput as Widget to minimize the impact on the forms, in the Admin this produce a side effect to have the label Version without any value, you should use this widget to display the current revision number

Exceptions

VersionChangedError

class concurrency.exceptions.VersionChangedError(message, code=None, params=None)[source]
class concurrency.exceptions.RecordModifiedError

RecordModifiedError

class concurrency.exceptions.RecordModifiedError(*args, **kwargs)[source]

InconsistencyError

Warning

removed in 0.7

class concurrency.exceptions.InconsistencyError

VersionError

class concurrency.exceptions.VersionError(message=None, code=None, params=None, *args, **kwargs)[source]

Admin

ConcurrentModelAdmin

class concurrency.admin.ConcurrentModelAdmin(model, admin_site)[source]

Warning

If you customize fields or fieldsets remember to add version field to the list. (See issue #81)

ConcurrencyActionMixin

class concurrency.admin.ConcurrencyActionMixin[source]

ConcurrencyListEditableMixin

class concurrency.admin.ConcurrencyListEditableMixin[source]

Middleware

class concurrency.middleware.ConcurrencyMiddleware

ConcurrencyMiddleware

class concurrency.middleware.ConcurrencyMiddleware(get_response=None)[source]

Intercept RecordModifiedError and invoke a callable defined in CONCURRECY_HANDLER409 passing the request and the object.

concurrency.views.conflict

concurrency.views.conflict(request, target=None, template_name=u'409.html')[source]

409 error handler.

Parameters:
  • request – Request
  • template_name409.html
  • target – The model to save

Helpers

apply_concurrency_check()

Add concurrency check to existing classes.

Note

With Django 1.7 and the new migrations management, this utility does not work anymore. To add concurrency management to a external Model, you need to use a migration to add a VersionField to the desired Model.

Note

See demo.auth_migrations for a example how to add IntegerVersionField to auth.Group )

operations = [
    # add version to django.contrib.auth.Group
    migrations.AddField(
        model_name='Group',
        name='version',
        field=IntegerVersionField(help_text=b'Version', default=1),
    ),
]

and put in your settings.py

MIGRATION_MODULES = {
    ...
    ...
    'auth': '<new.migration.package>',
}

disable_concurrency()

Context manager to temporary disable concurrency checking.

Starting from version 0.9, disable_concurrency can disable both at Model level or instance level, depending on the passed object. Passing Model is useful in django commands, load data or fixtures, where instance should be used by default

Is now possible use disable_concurrency without any argument to disable concurrency on any Model. This features has been developed to be used in django commands

Does not raise an exception if a model not under concurrency management is passed as argument.

examples

@disable_concurrency()
def recover_view(self, request, version_id, extra_context=None):
    return super(ReversionConcurrentModelAdmin, self).recover_view(request,
                                                        version_id,
                                                        extra_context)
def test_recover():
    deleted_list = revisions.get_deleted(ReversionConcurrentModel)
    delete_version = deleted_list.get(id=5)

    with disable_concurrency(ReversionConcurrentModel):
        deleted_version.revert()

concurrency_disable_increment()

Context manager to temporary disable version increment. Concurrent save is still checked but no version increment is triggered, this creates ‘shadow saves’,

It accepts both a Model or an instance as target.

Templatetags

identity

concurrency.templatetags.concurrency.identity(obj)[source]

returns a string representing “<pk>,<version>” of the passed object

version

concurrency.templatetags.concurrency.version(obj)[source]

returns the value of the VersionField of the passed object

is_version

concurrency.templatetags.concurrency.is_version(field)[source]

returns True if passed argument is a VersionField instance

Test Utilties

ConcurrencyTestMixin

class concurrency.utils.ConcurrencyTestMixin[source]

Mixin class to test Models that use VersionField

this class offer a simple test scenario. Its purpose is to discover some conflict in the save() inheritance:

from concurrency.utils import ConcurrencyTestMixin
from myproject.models import MyModel

class MyModelTest(ConcurrencyTestMixin, TestCase):
    concurrency_model = TestModel0
    concurrency_kwargs = {'username': 'test'}

Signining

VersionField is ‘displayed’ in the Form using an django.forms.HiddenInput widget, anyway to be sure that the version is not tampered with, its value is signed. The default VersionSigner is concurrency.forms.VersionFieldSigner that simply extends django.core.signing.Signer. If you want change your Signer you can set CONCURRENCY_FIELD_SIGNER in your settings

mysigner.py

class DummySigner():
    """ Dummy signer that simply returns the raw version value. (Simply do not sign it) """
    def sign(self, value):
        return smart_str(value)

    def unsign(self, signed_value):
        return smart_str(signed_value)

settings.py

CONCURRENCY_FIELD_SIGNER = "myapp.mysigner.DummySigner"