Django Concurrency
Django Concurrency
Release 2.4
Stefano Apostolico
1 Overview 1
2 How it works 3
3 Table Of Contents 5
4 Links 29
Index 31
i
ii
CHAPTER
ONE
OVERVIEW
1
Django Concurrency Documentation, Release 2.4
2 Chapter 1. Overview
CHAPTER
TWO
HOW IT WORKS
2.1 Overview
3
Django Concurrency Documentation, Release 2.4
THREE
TABLE OF CONTENTS
3.1 Install
Using pip:
django-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
5
Django Concurrency Documentation, Release 2.4
3.2 Fields
• VersionField
• IntegerVersionField
• AutoIncVersionField
• TriggerVersionField
– trigger_name
– triggers management command
• ConditionalVersionField
3.2.1 VersionField
3.2.2 IntegerVersionField
3.2.3 AutoIncVersionField
3.2.4 TriggerVersionField
class concurrency.fields.TriggerVersionField
This field use a database trigger to update the version field. Using this you can control external updates (ie using tools
like phpMyAdmin, pgAdmin, SQLDeveloper). The trigger is automatically created during syncdb() or you can use
the triggers management command.
Changed in version 1.0.
Warning: Before django-concurrency 1.0 two triggers per field were created, if you are upgrading you must
manually remove old triggers and recreate them using triggers management command
trigger_name
'concurrency_[DBTABLENAME]_[FIELDNAME]'
DATABASE TRIGGERS
default concurrency_concurrency_triggerconcurrentmodel_u
3.2.5 ConditionalVersionField
class User(models.Model):
version = ConditionalVersionField()
username = models.CharField(...)
password = models.PasswordField(...)
class ConcurrencyMeta:
check_fields = ('username',)
3.3 ConcurrencyMiddleware
3.3. ConcurrencyMiddleware 7
Django Concurrency Documentation, Release 2.4
MIDDLEWARE_CLASSES=('django.middleware.common.CommonMiddleware',
'concurrency.middleware.ConcurrencyMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware')
CONCURRENCY_HANDLER409 = 'demoproject.demoapp.views.conflict'
CONCURRENCY_POLICY = 2 # CONCURRENCY_LIST_EDITABLE_POLICY_ABORT_ALL
views.py
409.html
{% load concurrency %}
<table>
<tr>
<th>
Field
</th>
<th>
Current
(continues on next page)
</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 ConcurrencyMiddleware in the admin and you are using concurrency.admin.
ConcurrentModelAdmin remember to set your ModelAdmin to NOT use concurrency.forms.
ConcurrentForm
class MyModelAdmin(ConcurrentModelAdmin):
form = forms.ModelForm # overrides default ConcurrentForm
• Handle list_editable
• Check admin’s action execution for concurrency
• Update existing actions templates to be managed by concurrency
3.5 API
• Forms
– ConcurrentForm
– VersionWidget
• Exceptions
– VersionChangedError
– RecordModifiedError
– InconsistencyError
– VersionError
• Admin
– ConcurrentModelAdmin
– ConcurrencyActionMixin
– ConcurrencyListEditableMixin
• Middleware
– ConcurrencyMiddleware
– concurrency.views.conflict
• Helpers
– apply_concurrency_check()
– disable_concurrency()
* examples
– concurrency_disable_increment()
• Templatetags
– identity
– version
– is_version
• Triggers
– TriggerFactory
• Test Utilties
– ConcurrencyTestMixin
• Signining
3.5.1 Forms
ConcurrentForm
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 produce
a side effect to have the label Version without any value, you should use this widget to display the current revision
number
3.5. API 11
Django Concurrency Documentation, Release 2.4
3.5.2 Exceptions
VersionChangedError
RecordModifiedError
InconsistencyError
class concurrency.exceptions.InconsistencyError
VersionError
3.5.3 Admin
ConcurrentModelAdmin
Warning: If you customize fields or fieldsets remember to add version field to the list. (See issue issue
#81)
ConcurrencyActionMixin
class concurrency.admin.ConcurrencyActionMixin
ConcurrencyListEditableMixin
class concurrency.admin.ConcurrencyListEditableMixin
3.5.4 Middleware
class concurrency.middleware.ConcurrencyMiddleware
ConcurrencyMiddleware
See also:
ConcurrencyMiddleware
class concurrency.middleware.ConcurrencyMiddleware(get_response=None)
Intercept RecordModifiedError and invoke a callable defined in CONCURRECY_HANDLER409 passing the re-
quest and the object.
concurrency.views.conflict
3.5.5 Helpers
apply_concurrency_check()
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.
operations = [
# add version to django.contrib.auth.Group
migrations.AddField(
model_name='Group',
name='version',
(continues on next page)
3.5. API 13
Django Concurrency Documentation, Release 2.4
MIGRATION_MODULES = {
...
...
'auth': '<new.migration.package>',
}
disable_concurrency()
examples
@disable_concurrency()
def recover_view(self, request, version_id, extra_context=None):
return super().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()
3.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
3.5.7 Triggers
TriggerFactory
self.update_clause.format(trigger_name=field.trigger_name,
opts=field.model._meta,
field=field)
So as example:
3.5. API 15
Django Concurrency Documentation, Release 2.4
See also:
TRIGGERS_FACTORY
ConcurrencyTestMixin
class concurrency.utils.ConcurrencyTestMixin
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:
3.5.9 Signining
class DummySigner():
""" Dummy signer that simply returns the raw version value. (Simply do
˓→not sign it) """
settings.py
CONCURRENCY_FIELD_SIGNER = "myapp.mysigner.DummySigner"
3.6 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
3.6.1 AUTO_CREATE_TRIGGERS
3.6.2 ENABLED
3.6.3 CALLBACK
3.6.4 FIELD_SIGNER
3.6.5 HANDLER409
3.6. Settings 17
Django Concurrency Documentation, Release 2.4
3.6.6 IGNORE_DEFAULT
3.6.7 VERSION_FIELD_REQUIRED
3.6.8 MANUAL_TRIGGERS
3.6.9 POLICY
CONCURRENCY_LIST_EDITABLE_POLICY_SILENT
Used by admin’s integrations to handle list_editable conflicts. Do not save conflicting records, continue and
save 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 man-
agement you must set this flag.
See also:
Handle list_editable, ConcurrencyMiddleware
3.6.10 TRIGGERS_FACTORY
{'postgresql': "concurrency.triggers.PostgreSQL",
'mysql': "concurrency.triggers.MySQL",
'sqlite3': "concurrency.triggers.Sqlite3",
'sqlite': "concurrency.triggers.Sqlite3",
}
dict to customise TriggerFactory. Use this to customise the SQL clause to create triggers.
3.7 Cookbook
Sometimes you need to temporary disable concurrency (ie during data imports)
Temporary disable per Model
with disable_concurrency(instance):
Model.object
models.py
tests.py
a = ConcurrentModel.objects.get(pk=1)
b = ConcurrentModel.objects.get(pk=1)
a.save()
b.save() # this will raise ``RecordModifiedError``
3.7. Cookbook 19
Django Concurrency Documentation, Release 2.4
ConcurrencyTestMixin offer a very simple test function for your existing models
Recovering deleted records with diango-reversion produces a RecordModifiedError, because both pk and ver-
sion are present in the object, and django-concurrency tries to load the record (that does not exist), which raises
RecordModifiedError then.
To avoid this simply disable concurrency, by using a mixin:
class ConcurrencyVersionAdmin(reversion.admin.VersionAdmin):
@disable_concurrency()
def revision_view(self, request, object_id, version_id, extra_context=None):
return super().revision_view(request, object_id, version_id, extra_
˓→context=None)
@disable_concurrency()
def recover_view(self, request, version_id, extra_context=None):
return super().recover_view(request, version_id, extra_context)
3.8 Changelog
• Release 2.4
• Release 2.3
• Release 2.2
• Release 2.1.1
• Release 2.1 ( not released on pypi)
• Release 2.0
• Release 1.4 (13 Sep 2016)
• Release 1.3.2 (10 Sep 2016)
• Release 1.3.1 (15 Jul 2016)
• Release 1.3 (15 Jul 2016)
• Release 1.2 (05 Apr 2016)
• Release 1.1 (13 Feb 2016)
• Release 1.0.1
• Release 1.0
• Release 0.9
• Release 0.8.1
• Release 0.8
• Release 0.7.1
• Release 0.7
• Release 0.6.0
• Release 0.5.0
• Release 0.4.0
• Release 0.3.2
3.8. Changelog 21
Django Concurrency Documentation, Release 2.4
• fixes packaging
• fixes bug in ConditionalVersionField that produced ‘maximum recursion error’ when a model had a Many-
ToManyField with a field to same model (self-relation)
• just packaging
3.8. Changelog 23
Django Concurrency Documentation, Release 2.4
• backward compatibility updates. Do not check for concurrency if 0 is passed as version value (ie. no
value provided by the form)
• new concurrency.fields.TriggerVersionField
• start using pytest
• moved tests outside main package
• new protocol see:ref:protocols
• it’s now possible disable concurrency in Models that extends concurrency enabled models
• fixed issue #23 (thanks matklad)
• new USE_SELECT_FOR_UPDATE
3.8. Changelog 25
Django Concurrency Documentation, Release 2.4
3.9 FAQ
Use CONCURRENCY_IGNORE_DEFAULT accordingly or be sure that serializer does not set 0 as initial value
3.9.2 Just added django-concurrency to existing project and it does not work
Check that your records do not have 0 as version number and use CONCURRENCY_IGNORE_DEFAULT accordingly
South support has been removed after version 1.0 when Django <1.6 support has been removed as well.
If needed add these lines to your models.py:
It is possible to use save(update_fields=. . . ) parameter without interfree with the concurrency check algorithm
x1 = MyModel.objects.create(name='abc')
x2 = MyModel.objects.get(pk=x1.pk)
x1.save()
x2.save(update_fields=['username']) # raise RecordModifiedError
x1 = MyModel.objects.create(name='abc')
x2 = MyModel.objects.get(pk=x1.pk)
3.9. FAQ 27
Django Concurrency Documentation, Release 2.4
FOUR
LINKS
29
Django Concurrency Documentation, Release 2.4
30 Chapter 4. Links
INDEX
C I
CONCURRECY_MANUAL_TRIGGERS identity
setting, 18 template filter, 15
concurrency.exceptions.InconsistencyError identity() (in module concur-
(built-in class), 12 rency.templatetags.concurrency), 15
concurrency.exceptions.RecordModifiedError IntegerVersionField (class in concurrency.fields),
(built-in class), 12 6
concurrency.fields.TriggerVersionField is_version
(built-in class), 6 template filter, 15
concurrency.middleware.ConcurrencyMiddleware is_version() (in module concur-
(built-in class), 13 rency.templatetags.concurrency), 15
CONCURRENCY_AUTO_CREATE_TRIGGERS
setting, 16 M
CONCURRENCY_CALLBACK MANUAL_TRIGGERS
setting, 17 setting, 18
CONCURRENCY_ENABLED
setting, 17 R
CONCURRENCY_FIELD_SIGNER RecordModifiedError (class in concur-
setting, 17 rency.exceptions), 12
CONCURRENCY_HANDLER409
setting, 17 S
CONCURRENCY_IGNORE_DEFAULT setting
setting, 17 CONCURRECY_MANUAL_TRIGGERS, 18
CONCURRENCY_POLICY CONCURRENCY_AUTO_CREATE_TRIGGERS, 16
setting, 18 CONCURRENCY_CALLBACK, 17
CONCURRENCY_TRIGGERS_FACTORY CONCURRENCY_ENABLED, 17
setting, 18 CONCURRENCY_FIELD_SIGNER, 17
CONCURRENCY_VERSION_FIELD_REQUIRED CONCURRENCY_HANDLER409, 17
setting, 18 CONCURRENCY_IGNORE_DEFAULT, 17
ConcurrencyActionMixin (class in concur- CONCURRENCY_POLICY, 18
rency.admin), 12 CONCURRENCY_TRIGGERS_FACTORY, 18
ConcurrencyListEditableMixin (class in con- CONCURRENCY_VERSION_FIELD_REQUIRED,
currency.admin), 13 18
ConcurrencyMiddleware (class in concur- MANUAL_TRIGGERS, 18
rency.middleware), 13 TRIGGERS_FACTORY, 18
ConcurrencyTestMixin (class in concur-
rency.utils), 16 T
ConcurrentForm (class in concurrency.forms), 11 template filter
31
Django Concurrency Documentation, Release 2.4
identity, 15
is_version, 15
version, 15
trigger_name (TriggerVersionField attribute), 7
TriggerFactory (class in concurrency.triggers), 15
TRIGGERS_FACTORY
setting, 18
V
version
template filter, 15
version() (in module concur-
rency.templatetags.concurrency), 15
VersionChangedError (class in concur-
rency.exceptions), 12
VersionError (class in concurrency.exceptions), 12
VersionField (class in concurrency.fields), 6
VersionWidget (class in concurrency.forms), 11
32 Index