Skip to content

Commit fbd3762

Browse files
committed
fixes issue in ConcurrentModelAdminon standard updates
- add example and documentation of ConcurrencyMiddleware - new teplate filter `is_version`
1 parent df38e19 commit fbd3762

File tree

18 files changed

+303
-42
lines changed

18 files changed

+303
-42
lines changed

concurrency/admin.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@
55
from django.core.exceptions import ImproperlyConfigured, ValidationError
66
from django.db.models import Q
77
from django.forms.formsets import (ManagementForm, TOTAL_FORM_COUNT, INITIAL_FORM_COUNT,
8-
MAX_NUM_FORM_COUNT)
9-
from django.forms.models import BaseModelFormSet
8+
MAX_NUM_FORM_COUNT)
9+
from django.forms.models import BaseModelFormSet, modelform_factory
1010
from django.utils.safestring import mark_safe
1111
from django.contrib.admin import helpers
1212
from django.http import HttpResponse, HttpResponseRedirect
1313
from django.utils.translation import ugettext as _
14-
from concurrency.api import get_revision_of_object
15-
from concurrency.config import conf, CONCURRENCY_LIST_EDITABLE_POLICY_SILENT
14+
from concurrency import forms
15+
from concurrency.api import get_revision_of_object, get_version_fieldname
16+
from concurrency.config import conf, CONCURRENCY_POLICY_SILENT
1617
from concurrency.exceptions import RecordModifiedError
18+
from concurrency.forms import ConcurrentForm, VersionWidget
1719

1820

1921
class ConcurrencyActionMixin(object):
@@ -146,19 +148,21 @@ def _management_form(self):
146148

147149

148150
class ConcurrencyListEditableMixin(object):
149-
list_editable_policy = conf.LIST_EDITABLE_POLICY
151+
list_editable_policy = conf.POLICY
150152

151153
def get_changelist_formset(self, request, **kwargs):
152154
kwargs['formset'] = ConcurrentBaseModelFormSet
153155
return super(ConcurrencyListEditableMixin, self).get_changelist_formset(request, **kwargs)
154156

155157
def save_model(self, request, obj, form, change):
156158
try:
157-
if obj.pk:
158-
obj.version = int(request.POST['_concurrency_version_{0.pk}'.format(obj)])
159+
if change:
160+
version = request.POST.get('_concurrency_version_{0.pk}'.format(obj), None)
161+
if version:
162+
obj.version = int(version)
159163
super(ConcurrencyListEditableMixin, self).save_model(request, obj, form, change)
160164
except RecordModifiedError:
161-
if self.list_editable_policy == CONCURRENCY_LIST_EDITABLE_POLICY_SILENT:
165+
if self.list_editable_policy == CONCURRENCY_POLICY_SILENT:
162166
messages.error(request, _("Record with pk `{0.pk}` has been modified and was not updated").format(obj))
163167
else:
164168
raise
@@ -167,4 +171,5 @@ def save_model(self, request, obj, form, change):
167171
class ConcurrentModelAdmin(ConcurrencyActionMixin,
168172
ConcurrencyListEditableMixin,
169173
admin.ModelAdmin):
170-
pass
174+
form = ConcurrentForm
175+
formfield_overrides = {forms.VersionField: {'widget': VersionWidget},}

concurrency/api.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
logger = logging.getLogger(__name__)
1414

1515

16+
def get_version_fieldname(obj):
17+
return obj.RevisionMetaInfo.field.attname
18+
19+
1620
def get_revision_of_object(obj):
1721
"""
1822
returns teh version of the passed object

concurrency/config.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
# 0 do not save updated records, save others, show message to the user
1010
# 1 abort whole transaction
1111

12-
CONCURRENCY_LIST_EDITABLE_POLICY_SILENT = 0
13-
CONCURRENCY_LIST_EDITABLE_POLICY_ABORT_ALL = 1
14-
12+
CONCURRENCY_POLICY_SILENT = 0
13+
CONCURRENCY_POLICY_ABORT_ALL = 1
14+
CONCURRENCY_POLICY_RAISE = 2
1515

1616
class AppSettings(object):
1717
"""
@@ -43,7 +43,7 @@ class AppSettings(object):
4343
defaults = {
4444
'SANITY_CHECK': True,
4545
'FIELD_SIGNER': 'concurrency.forms.VersionFieldSigner',
46-
'LIST_EDITABLE_POLICY': CONCURRENCY_LIST_EDITABLE_POLICY_SILENT,
46+
'POLICY': CONCURRENCY_POLICY_SILENT,
4747
'HANDLER409': 'concurrency.views.conflict'}
4848

4949
def __init__(self, prefix):

concurrency/exceptions.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@ class InconsistencyError(DatabaseError):
1919

2020

2121
class VersionError(SuspiciousOperation):
22-
def __init__(self, message=None, code=None, params=None):
22+
23+
def __init__(self, message=None, code=None, params=None, *args, **kwargs):
2324
self.message = message or _("Version number is missing or has been tampered with")

concurrency/fields.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def get_internal_type(self):
6767

6868
def pre_save(self, model_instance, add):
6969
old_value = getattr(model_instance, self.attname, 0)
70-
value = max(int(old_value )+ 1, (int(time.time() * 1000000) - OFFSET))
70+
value = max(int(old_value) + 1, (int(time.time() * 1000000) - OFFSET))
7171
setattr(model_instance, self.attname, value)
7272
return value
7373

concurrency/forms.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from __future__ import absolute_import, unicode_literals
22
from django import forms
3-
from django.conf import settings
43
from django.core import validators
54
from django.core.exceptions import NON_FIELD_ERRORS, ImproperlyConfigured
65
from django.core.signing import Signer, BadSignature

concurrency/middleware.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
from __future__ import absolute_import, unicode_literals
33
from django.core.signals import got_request_exception
44
from django.core.urlresolvers import get_callable
5+
from concurrency.config import conf
56
from concurrency.core import RecordModifiedError
6-
from concurrency.views import handler409
77

88

99
class ConcurrencyMiddleware(object):
@@ -14,5 +14,5 @@ class ConcurrencyMiddleware(object):
1414
def process_exception(self, request, exception):
1515
if isinstance(exception, RecordModifiedError):
1616
got_request_exception.send(sender=self, request=request)
17-
callback = get_callable(handler409)
17+
callback = get_callable(conf.HANDLER409)
1818
return callback(request, target=exception.target)

concurrency/templatetags/concurrency.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from django.templatetags.l10n import unlocalize
44
from django.utils.safestring import mark_safe
55
from concurrency.api import get_revision_of_object
6+
from concurrency.fields import VersionField
67

78
register = Library()
89

@@ -15,3 +16,8 @@ def identity(obj):
1516
@register.filter
1617
def version(obj):
1718
return get_revision_of_object(obj)
19+
20+
21+
@register.filter
22+
def is_version(field):
23+
return isinstance(field, VersionField)

concurrency/tests/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from concurrency.tests.conf import SettingsTest # NOQA
55
from concurrency.tests.api import ConcurrencyTestApi # NOQA
66

7-
from concurrency.tests.admin_edit import TestAdminEdit # NOQA
7+
from concurrency.tests.admin_edit import TestAdminEdit, TestConcurrentModelAdmin # NOQA
88
from concurrency.tests.admin_list_editable import TestListEditable, TestListEditableWithNoActions # NOQA
99
from concurrency.tests.admin_actions import TestAdminActions # NOQA
1010

concurrency/tests/admin_edit.py

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,60 @@
1+
from django.contrib.admin import site, ModelAdmin
12
from django.core.urlresolvers import reverse
23
from django.utils.translation import ugettext as _
4+
from concurrency.admin import ConcurrentModelAdmin
35
from concurrency.forms import VersionFieldSigner
46
from concurrency.tests.base import AdminTestCase, SENTINEL
5-
from concurrency.tests.models import TestModel1, TestModel0
7+
from concurrency.tests.models import TestModel1, TestModel0, ConcurrentModel
8+
9+
10+
class TestConcurrentModelAdmin(AdminTestCase):
11+
def setUp(self):
12+
super(TestConcurrentModelAdmin, self).setUp()
13+
assert isinstance(site._registry[ConcurrentModel], ConcurrentModelAdmin)
14+
15+
def test_standard_update(self):
16+
target, __ = ConcurrentModel.objects.get_or_create(dummy_char='aaa')
17+
url = reverse('admin:concurrency_concurrentmodel_change', args=[target.pk])
18+
res = self.app.get(url, user='sax')
19+
target = res.context['original']
20+
old_version = target.version
21+
form = res.form
22+
form['dummy_char'] = 'UPDATED'
23+
res = form.submit().follow()
24+
target = ConcurrentModel.objects.get(pk=target.pk)
25+
new_version = target.version
26+
self.assertGreater(new_version, old_version)
27+
28+
def test_creation(self):
29+
url = reverse('admin:concurrency_concurrentmodel_add')
30+
res = self.app.get(url, user='sax')
31+
form = res.form
32+
form['dummy_char'] = 'CHAR'
33+
res = form.submit().follow()
34+
self.assertTrue(ConcurrentModel.objects.filter(dummy_char='CHAR').exists())
35+
self.assertGreater(ConcurrentModel.objects.get(dummy_char='CHAR').version, 0)
36+
37+
def test_conflict(self):
38+
target, __ = ConcurrentModel.objects.get_or_create(dummy_char='aaa')
39+
url = reverse('admin:concurrency_concurrentmodel_change', args=[target.pk])
40+
res = self.app.get(url, user='sax')
41+
form = res.form
42+
43+
target.save() # create conflict here
44+
45+
res = form.submit()
46+
self.assertIn('original', res.context)
47+
self.assertTrue(res.context['adminform'].form.errors,
48+
res.context['adminform'].form.errors)
49+
self.assertIn(_('Record Modified'),
50+
str(res.context['adminform'].form.errors),
51+
res.context['adminform'].form.errors)
652

753

854
class TestAdminEdit(AdminTestCase):
55+
def setUp(self):
56+
super(TestAdminEdit, self).setUp()
57+
assert isinstance(site._registry[TestModel1], ModelAdmin)
958

1059
def _create_conflict(self, pk):
1160
u = TestModel1.objects.get(pk=pk)
@@ -42,7 +91,8 @@ def test_standard_update(self):
4291
target = res.context['original']
4392
old_version = target.version
4493
form = res.form
45-
form.submit().follow()
94+
form['dummy_char'] = 'UPDATED'
95+
res = form.submit().follow()
4696
target = TestModel0.objects.get(pk=target.pk)
4797
new_version = target.version
4898
self.assertGreater(new_version, old_version)

0 commit comments

Comments
 (0)