django concurrency documentation · •check admin’s action execution for concurrency •update...

25
Django Concurrency Documentation Release 0.6 Stefano Apostolico January 04, 2014

Upload: others

Post on 21-Jul-2020

26 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Django Concurrency Documentation · •Check admin’s action execution for concurrency •Update existing actions templates to be managed by concurrency 4.4.1Handle list_editable

Django Concurrency DocumentationRelease 0.6

Stefano Apostolico

January 04, 2014

Page 2: Django Concurrency Documentation · •Check admin’s action execution for concurrency •Update existing actions templates to be managed by concurrency 4.4.1Handle list_editable
Page 3: Django Concurrency Documentation · •Check admin’s action execution for concurrency •Update existing actions templates to be managed by concurrency 4.4.1Handle list_editable

Contents

i

Page 4: Django Concurrency Documentation · •Check admin’s action execution for concurrency •Update existing actions templates to be managed by concurrency 4.4.1Handle list_editable

ii

Page 5: Django Concurrency Documentation · •Check admin’s action execution for concurrency •Update existing actions templates to be managed by concurrency 4.4.1Handle list_editable

CHAPTER 1

Overview

django-concurrency is an optimistic locking library for Django Models

It prevents users from doing concurrent editing in Django both from UI and from a django command.

Note: Concurrency requires Django >= 1.4

• easy to add to existing Models ( just add VersionField )

• works with Django internal models

• works with third-party models

• minimum intervention with existing database

• handle http post and standard python code (ie. django management commands)

• complete test suite (Test suite)

• works with South and diango-reversion

• Admin integration (handle actions and list_editable)

1

Page 6: Django Concurrency Documentation · •Check admin’s action execution for concurrency •Update existing actions templates to be managed by concurrency 4.4.1Handle list_editable

Django Concurrency Documentation, Release 0.6

2 Chapter 1. Overview

Page 7: Django Concurrency Documentation · •Check admin’s action execution for concurrency •Update existing actions templates to be managed by concurrency 4.4.1Handle list_editable

CHAPTER 2

Todo

• intercept external updates (ie changes done using DB browser like SQLDeveloper, pgAdmin, phpMyAd-min...)

3

Page 8: Django Concurrency Documentation · •Check admin’s action execution for concurrency •Update existing actions templates to be managed by concurrency 4.4.1Handle list_editable

Django Concurrency Documentation, Release 0.6

4 Chapter 2. Todo

Page 9: Django Concurrency Documentation · •Check admin’s action execution for concurrency •Update existing actions templates to be managed by concurrency 4.4.1Handle list_editable

CHAPTER 3

How it works

Concurrency works using a version field added to each model, each time a record is saved the version number changes(the algorithm used depends on the VersionField used, (see Fields).

When a record is saved, Concurrency tries to get a lock on the record based on the old revision number, if the recordis not found a RecordModifiedError is raised

5

Page 10: Django Concurrency Documentation · •Check admin’s action execution for concurrency •Update existing actions templates to be managed by concurrency 4.4.1Handle list_editable

Django Concurrency Documentation, Release 0.6

6 Chapter 3. How it works

Page 11: Django Concurrency Documentation · •Check admin’s action execution for concurrency •Update existing actions templates to be managed by concurrency 4.4.1Handle list_editable

CHAPTER 4

Table Of Contents

4.1 Install

Using pip:

pip install django-concurrency

Go to https://github.com/saxix/django-concurrency if you need to download a package or clone the repo.

Concurrency does not need to be added into INSTALLED_APPS unless you want to run the tests

4.1.1 Test suite

Concurrency comes with a set of tests that can simulate different scenarios

• basic versioned model

• inherited model

• inherited model from abstract model

• inherited model from external project model

• django User model

• models with custom save

• proxy models

• admin actions

How to run the tests

Option 1: using tox

$ pip install tox$ tox

7

Page 12: Django Concurrency Documentation · •Check admin’s action execution for concurrency •Update existing actions templates to be managed by concurrency 4.4.1Handle list_editable

Django Concurrency Documentation, Release 0.6

Option 2: using demo project

$ cd demo$ pip install -r demoproject/requirements.pip$ ./manage.py test concurrency demoapp

Option 3: execute in your project

simply add concurrency in your INSTALLED_APPS

INSTALLED_APPS = (’concurrency’,...

)

4.2 Fields

• IntegerVersionField• AutoIncVersionField

4.2.1 IntegerVersionField

class concurrency.fields.IntegerVersionField(**kwargs)Version Field that returns a “unique” version number for the record.

The version number is produced using time.time() * 1000000, to get the benefits of microsecond if the systemclock provides them.

4.2.2 AutoIncVersionField

class concurrency.fields.AutoIncVersionField(**kwargs)Version Field increment the revision number each commit

4.3 ConcurrencyMiddleware

You can globally intercept RecordModifiedError adding concurrency.middleware.ConcurrencyMiddleware to yourMIDDLEWARE_CLASSES. Each time a RecordModifiedError is raised it goes up to the ConcurrencyMiddle-ware and the handler defined in CONCURRENCY_HANDLER409 is invoked.

Example

settings.py

MIDDLEWARE_CLASSES=(’django.middleware.common.CommonMiddleware’,’concurrency.middleware.ConcurrencyMiddleware’,’django.contrib.sessions.middleware.SessionMiddleware’,’django.middleware.csrf.CsrfViewMiddleware’,’django.contrib.auth.middleware.AuthenticationMiddleware’,

8 Chapter 4. Table Of Contents

Page 13: Django Concurrency Documentation · •Check admin’s action execution for concurrency •Update existing actions templates to be managed by concurrency 4.4.1Handle list_editable

Django Concurrency Documentation, Release 0.6

’django.contrib.messages.middleware.MessageMiddleware’)

CONCURRENCY_HANDLER409 = ’demoproject.demoapp.views.conflict’CONCURRENCY_POLICY = 2 # CONCURRENCY_LIST_EDITABLE_POLICY_ABORT_ALL

views.py

from diff_match_patch import diff_match_patchfrom concurrency.views import ConflictResponsefrom django.template import loaderfrom django.utils.safestring import mark_safefrom django.template.context import RequestContext

def get_diff(current, stored):data = []dmp = diff_match_patch()fields = current._meta.fieldsfor field in fields:

v1 = getattr(current, field.name, "")v2 = getattr(stored, field.name, "")diff = dmp.diff_main(unicode(v1), unicode(v2))dmp.diff_cleanupSemantic(diff)html = dmp.diff_prettyHtml(diff)html = mark_safe(html)data.append((field, v1, v2, html))

return data

def conflict(request, target=None, template_name=’409.html’):template = loader.get_template(template_name)try:

saved = target.__class__._default_manager.get(pk=target.pk)diff = get_diff(target, saved)

except target.__class__.DoesNotExists:saved = Nonediff = None

ctx = RequestContext(request, {’target’: target,’diff’: diff,’saved’: saved,’request_path’: request.path})

return ConflictResponse(template.render(ctx))

409.html

{% load concurrency %}<table>

<tr><th>

Field</th><th>

Current</th><th>

Stored</th><th>

Diff</th>

4.3. ConcurrencyMiddleware 9

Page 14: Django Concurrency Documentation · •Check admin’s action execution for concurrency •Update existing actions templates to be managed by concurrency 4.4.1Handle list_editable

Django Concurrency Documentation, Release 0.6

</tr><tr>

{% for field, current, stored, entry in diff %}{% if not field.primary_key and not field|is_version %}

<tr><td>

{{ field.verbose_name }}</td><td>

{{ current }}</td><td>

{{ stored }}</td><td>

{{ entry }}</td>

</tr>{% endif %}

{% endfor %}</tr>

</table>

If you want to use ConcurrentMiddleware in the admin and you are using ConcurrentModelAdmin remember to setyour ModelAdmin to NOT use ConcurrentForm

from django import forms

class MyModelAdmin(ConcurrentModelAdmin):form = forms.ModelForm # overrides default ConcurrentForm

4.4 Admin Integration

• Handle list_editable• Check admin’s action execution for concurrency• Update existing actions templates to be managed by concurrency

4.4.1 Handle list_editable

New in version 0.6. Concurrency is able to handle conflicts in the admin’s changelist view whenModelAdmin.list_editable is enabled. To enable this feature simply extend your ModelAdmin from Con-currentModelAdmin or use ConcurrencyListEditableMixin

See Also:

CONCURRENCY_LIST_EDITABLE_POLICY_SILENT

4.4.2 Check admin’s action execution for concurrency

New in version 0.6. Extend your ModelAdmin with ConcurrencyActionMixin or use ConcurrentModelAdmin

10 Chapter 4. Table Of Contents

Page 15: Django Concurrency Documentation · •Check admin’s action execution for concurrency •Update existing actions templates to be managed by concurrency 4.4.1Handle list_editable

Django Concurrency Documentation, Release 0.6

4.4.3 Update existing actions templates to be managed by concurrency

New in version 0.6. You ca use the identity filter to pass both pk and version to your Mod-elAdmin. Each time you use {{ obj.pk }} simply change to {{ obj|identity }}. So in theadmin/delete_selected_confirmation.html will have:

{% for obj in queryset %}<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj|identity }}" />{% endfor %}

4.5 API

• Forms– ConcurrentForm– VersionWidget

• Exceptions– VersionChangedError– RecordModifiedError– InconsistencyError– VersionError

• Admin– ConcurrentModelAdmin– ConcurrencyActionMixin– ConcurrencyListEditableMixin

• Middleware– ConcurrencyMiddleware– concurrency.views.conflict()

• Helpers– concurrency_check()– apply_concurrency_check()– disable_concurrency()– disable_sanity_check()

• Templatetags– identity– version– is_version

• Test Utilties– ConcurrencyTestMixin

• Signining

4.5.1 Forms

ConcurrentForm

class concurrency.forms.ConcurrentForm(data=None, files=None, auto_id=’id_%s’, pre-fix=None, initial=None, error_class=<class‘django.forms.util.ErrorList’>, label_suffix=’:’,empty_permitted=False, instance=None)

Simple wrapper to ModelForm that try to mitigate some concurrency error. Note that is always possible have

4.5. API 11

Page 16: Django Concurrency Documentation · •Check admin’s action execution for concurrency •Update existing actions templates to be managed by concurrency 4.4.1Handle list_editable

Django Concurrency Documentation, Release 0.6

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)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 producea side effect to have the label Version without any value, you should use this widget to display the current revisionnumber

4.5.2 Exceptions

VersionChangedError

class concurrency.exceptions.VersionChangedError(message, code=None, params=None)

RecordModifiedError

class concurrency.exceptions.RecordModifiedError(*args, **kwargs)

InconsistencyError

class concurrency.exceptions.InconsistencyError

VersionError

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

4.5.3 Admin

ConcurrentModelAdmin

class concurrency.admin.ConcurrentModelAdmin(model, admin_site)

ConcurrencyActionMixin

class concurrency.admin.ConcurrencyActionMixin

ConcurrencyListEditableMixin

class concurrency.admin.ConcurrencyListEditableMixin

12 Chapter 4. Table Of Contents

Page 17: Django Concurrency Documentation · •Check admin’s action execution for concurrency •Update existing actions templates to be managed by concurrency 4.4.1Handle list_editable

Django Concurrency Documentation, Release 0.6

4.5.4 Middleware

ConcurrencyMiddleware

See Also:

ConcurrencyMiddleware

class concurrency.middleware.ConcurrencyMiddlewareIntercept RecordModifiedError and invoke a callable defined in CONCURRECY_HANDLER409 passing the re-quest and the object.

concurrency.views.conflict()

class concurrency.views.conflict409 error handler.

Templates: 409.html Context: target : The model to save saved : The object stored in the db that produce the

conflict or None if not found (ie. deleted)

request_path : The path of the requested URL (e.g., ‘/app/pages/bad_page/’)

4.5.5 Helpers

concurrency_check()

Sometimes, VersionField(s) cannot wrap the save() method, is these cirumstances you can check it manually

from concurrency.core import concurrency_check

class AbstractModelWithCustomSave(models.Model):version = IntegerVersionField(db_column=’cm_version_id’, manually=True)

def save(self, *args, **kwargs):concurrency_check(self, *args, **kwargs)super(SecurityConcurrencyBaseModel, self).save(*args, **kwargs)

Note: Please note manually=True argument in IntegerVersionField() definition

apply_concurrency_check()

New in version 0.4. Add concurrency check to existing classes.

concurrency.api.apply_concurrency_check(model, fieldname, versionclass)Apply concurrency management to existing Models.

Parameters

• model (django.db.Model) – Model class to update

• fieldname (basestring) – name of the field

• versionclass (concurrency.fields.VersionField subclass) –

4.5. API 13

Page 18: Django Concurrency Documentation · •Check admin’s action execution for concurrency •Update existing actions templates to be managed by concurrency 4.4.1Handle list_editable

Django Concurrency Documentation, Release 0.6

disable_concurrency()

New in version 0.6. Context manager to temporary disable concurrency checking

disable_sanity_check()

New in version 0.6. Context manager to disable sanity check checking for one model. see Unable to import data ?

4.5.6 Templatetags

identity

concurrency.templatetags.concurrency.identity(obj)returns a string representing “<pk>,<version>” of the passed object

version

concurrency.templatetags.concurrency.version(obj)returns the value of the VersionField of the passed object

is_version

concurrency.templatetags.concurrency.is_version(field)returns True if passed argument is a VersionField instance

4.5.7 Test Utilties

ConcurrencyTestMixin

class concurrency.utils.ConcurrencyTestMixinMixin 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 ConcurrencyTestMixinfrom myproject.models import MyModel

class MyModelTest(ConcurrencyTestMixin, TestCase):concurrency_model = TestModel0concurrency_kwargs = {’username’: ’test’}

4.5.8 Signining

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

mysigner.py

14 Chapter 4. Table Of Contents

Page 19: Django Concurrency Documentation · •Check admin’s action execution for concurrency •Update existing actions templates to be managed by concurrency 4.4.1Handle list_editable

Django Concurrency Documentation, Release 0.6

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"

4.6 Settings

4.6.1 Available settings

Here’s a full list of all available settings, in alphabetical order, and their default values.

Note: Each entry MUST have the prefix CONCURRENCY_ when used in your settings.py

CALLBACK

New in version 0.6. Default: None

Custom callable to handle conflicts

See Also:

CONCURRENCY_POLICY

FIELD_SIGNER

New in version 0.5. Default: concurrency.forms.VersionFieldSigner

Class used to sign the version numbers.

See Also:

Signining

HANDLER409

New in version 0.6. Default: concurrency.views.conflict

Handler to intercept RecordModifiedError into ConcurrencyMiddleware. The default implementation (concur-rency.views.conflict()) renders 409.htmlwhile passing into the context the object that is going to be saved (target)and the same object as stored in the database (saved)

See Also:

ConcurrencyMiddleware

4.6. Settings 15

Page 20: Django Concurrency Documentation · •Check admin’s action execution for concurrency •Update existing actions templates to be managed by concurrency 4.4.1Handle list_editable

Django Concurrency Documentation, Release 0.6

POLICY

New in version 0.6. Default: CONCURRENCY_POLICY_RAISE & CONCURRENCY_LIST_EDITABLE_POLICY_SILENT

CONCURRENCY_POLICY_RAISE

Default behaviour. Raises RecordModifiedError as detects a conflict.

CONCURRENCY_POLICY_CALLBACK

Demand the conflict management to the callable defined in CONCURRENCY_CALLBACK

CONCURRENCY_LIST_EDITABLE_POLICY_SILENT

Used by admin’s integrations to handle list_editable conflicts. Do not save conflicting records, continue andsave all non-conflicting records, show a message to the user

CONCURRENCY_LIST_EDITABLE_POLICY_ABORT_ALL

Used by admin’s integations to handle list_editable. Stop at the first conflict and raise RecordModifiedError.Note that if you want to use ConcurrencyMiddleware based conflict management you must set this flag.

See Also:

Handle list_editable, ConcurrencyMiddleware

SANITY_CHECK

New in version 0.4. Default: True

If you wand to disable the check raised when you try to save an object with a revision number set but without pk (thisshould not happen) you can set CONCURRECY_SANITY_CHECK=False in your settings.

This is useful if you have some existing test code that uses factories which create a random number thus preventingthe sanity check from passing

4.7 Cookbook

• Unable to import data ?• Add version management to new models• Add version management to Django and/or plugged in applications models• Manually handle concurrency• Test Utilities

16 Chapter 4. Table Of Contents

Page 21: Django Concurrency Documentation · •Check admin’s action execution for concurrency •Update existing actions templates to be managed by concurrency 4.4.1Handle list_editable

Django Concurrency Documentation, Release 0.6

4.7.1 Unable to import data ?

Concurrency check that any model, when saved, has no version, anyway this is not true when you are importing datafrom a file or loading a fixture. This is internally known as SANITY_CHECK. To solve this you can:

Globally disable:

edit your settings.py and add:

CONCURRENCY_SANITY_CHECK = False

Temporary disable

from concurrency.config import conf

conf.SANITY_CHECK = False

Temporary disable per Model

from concurrency.api import disable_sanity_check

with disable_sanity_check(Model):Model.object

4.7.2 Add version management to new models

models.py

from concurrency.fields import IntegerVersionField

class ConcurrentModel( models.Model ):version = IntegerVersionField( )

tests.py

a = ConcurrentModel.objects.get(pk=1)b = ConcurrentModel.objects.get(pk=1)a.save()b.save() # this will raise ‘‘RecordModifiedError‘‘

4.7.3 Add version management to Django and/or plugged in applications models

Changed in version 0.4. Concurrency can work even with existing models, anyway if you are adding concurrencymanagement to an existing database remember to edit the database’s tables:

your_app.models.py

from django.contrib.auth import Userfrom concurrency.api import apply_concurrency_check

apply_concurrency_check(User, ’version’, IntegerVersionField)

4.7.4 Manually handle concurrency

Changed in version 0.4. Use concurrency_check()

4.7. Cookbook 17

Page 22: Django Concurrency Documentation · •Check admin’s action execution for concurrency •Update existing actions templates to be managed by concurrency 4.4.1Handle list_editable

Django Concurrency Documentation, Release 0.6

from concurrency.api import concurrency_check

class AbstractModelWithCustomSave(models.Model):version = IntegerVersionField(db_column=’cm_version_id’, manually=True)

def save(self, *args, **kwargs):concurrency_check(self, *args, **kwargs)logger.debug(u’Saving %s "%s".’ % (self._meta.verbose_name, self))super(SecurityConcurrencyBaseModel, self).save(*args, **kwargs)

4.7.5 Test Utilities

ConcurrencyTestMixin offer a very simple test function for your existing models

from concurrency.utils import ConcurrencyTestMixinfrom myproject.models import MyModel

class MyModelTest(ConcurrencyTestMixin, TestCase):concurrency_model = TestModel0concurrency_kwargs = {’username’: ’test’}

4.8 Changelog

This section lists the biggest changes done on each release.

• Release 0.6.0• Release 0.5.0• Release 0.4.0• Release 0.3.2

4.8.1 Release 0.6.0

• new disable_concurrency() context manager

• added documentation for concurrency.middleware.ConcurrencyMiddleware

• BACKWARD INCOMPATIBLE Fixed typo: CONCURRECY_SANITY_CHECK nowCONCURRENCY_SANITY_CHECK

• added disable_sanity_check() context manager

• added configuration

• check admin actions for concurrent deletion

• added concurrency check for admin’s Handle list_editable

4.8.2 Release 0.5.0

• python 3.x compatibility

18 Chapter 4. Table Of Contents

Page 23: Django Concurrency Documentation · •Check admin’s action execution for concurrency •Update existing actions templates to be managed by concurrency 4.4.1Handle list_editable

Django Concurrency Documentation, Release 0.6

• new CONCURRENCY_FIELD_SIGNER

4.8.3 Release 0.4.0

• start deprecation of concurrency.core.VersionChangedError, concurrency.core.RecordModifiedError,concurrency.core.InconsistencyError,moved in concurrency.exceptions

• start deprecation of concurrency.core.apply_concurrency_check,concurrency.core.concurrency_check moved in concurrency.api

• added CONCURRECY_SANITY_CHECK settings entry

• signing of version number to avoid tampering (ConcurrentForm)

• added ConcurrencyTestMixin to help test on concurrency managed models

• changed way to add concurrency to exisiting models (apply_concurrency_check())

• fixed #4 (thanks FrankBie)

• removed RandomVersionField

• new concurrency_check()

• added ConcurrentForm to mitigate some concurrency conflict

• select_for_update now executed with nowait=True

• removed some internal methods, to avoid unlikely but possible name clashes

4.8.4 Release 0.3.2

• fixed #3 (thanks pombredanne)

• fixed #1 (thanks mbrochh)

4.8. Changelog 19

Page 24: Django Concurrency Documentation · •Check admin’s action execution for concurrency •Update existing actions templates to be managed by concurrency 4.4.1Handle list_editable

Django Concurrency Documentation, Release 0.6

20 Chapter 4. Table Of Contents

Page 25: Django Concurrency Documentation · •Check admin’s action execution for concurrency •Update existing actions templates to be managed by concurrency 4.4.1Handle list_editable

CHAPTER 5

Links

• Project home page: https://github.com/saxix/django-concurrency

• Issue tracker: https://github.com/saxix/django-concurrency/issues?sort

• Download: http://pypi.python.org/pypi/django-concurrency/

• Docs: http://readthedocs.org/docs/django-concurrency/en/latest/

21