Skip to content

Commit c4a949c

Browse files
committed
- bug fixes
- added middleware tests
1 parent b902bd7 commit c4a949c

File tree

9 files changed

+125
-24
lines changed

9 files changed

+125
-24
lines changed

concurrency/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import datetime
33
import os
44

5-
VERSION = __version__ = (0, 4, 0, 'rc', 1)
5+
VERSION = __version__ = (0, 4, 0, 'rc', 2)
66
__author__ = 'sax'
77

88

concurrency/core.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,10 @@ def concurrency_check(model_instance, force_insert=False, force_update=False, us
5353

5454
def _select_lock(model_instance, version_value=None):
5555
version_field = model_instance.RevisionMetaInfo.field
56-
value = getattr(model_instance, version_field.name)
56+
value = version_value or getattr(model_instance, version_field.name)
5757
is_versioned = value != version_field.get_default()
58-
5958
if model_instance.pk is not None:
60-
kwargs = {'pk': model_instance.pk,
61-
version_field.name: version_value or getattr(model_instance, version_field.name)}
59+
kwargs = {'pk': model_instance.pk, version_field.name: value}
6260
alias = router.db_for_write(model_instance)
6361
NOWAIT = connections[alias].features.has_select_for_update_nowait
6462
entry = model_instance.__class__.objects.select_for_update(nowait=NOWAIT).filter(**kwargs)

concurrency/forms.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ class VersionFieldSigner(Signer):
4848
pass
4949

5050

51+
class SignedValue(object):
52+
def __init__(self, value):
53+
self.value = value
54+
55+
def __repr__(self):
56+
return self.value
57+
58+
5159
class VersionField(forms.IntegerField):
5260
widget = HiddenInput # Default widget to use when rendering this type of Field.
5361
hidden_widget = HiddenInput# Default widget to use when rendering this as "hidden".
@@ -61,17 +69,20 @@ def __init__(self, *args, **kwargs):
6169
kwargs.setdefault('widget', HiddenInput)
6270
super(VersionField, self).__init__(*args, **kwargs)
6371

72+
def bound_data(self, data, initial):
73+
return SignedValue(data)
74+
6475
def prepare_value(self, value):
65-
if value:
66-
return self._signer.sign(value)
67-
return None
76+
if isinstance(value, SignedValue):
77+
return value
78+
return SignedValue(self._signer.sign(value))
6879

6980
def to_python(self, value):
7081
try:
7182
if value not in (None, '', 'None'):
7283
return int(self._signer.unsign(value))
7384
return 0
74-
except (BadSignature, ValueError):
85+
except (BadSignature, ValueError) as e:
7586
raise SuspiciousOperation(_('Version number seems tampered'))
7687

7788
def widget_attrs(self, widget):

concurrency/middleware.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from concurrency.views import handler409
55

66

7+
8+
# This middleware is still alpha and should not be used.
79
class ConcurrencyMiddleware(object):
810
def process_exception(self, request, exception):
911
if isinstance(exception, RecordModifiedError):

concurrency/tests/contrib_admin.py

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import re
12
from django.contrib import admin
23
import os
34

@@ -35,11 +36,11 @@ class TestModel1Admin(admin.ModelAdmin):
3536
widgets={'version': VersionWidget()})
3637

3738

38-
class TestDjangoAdmin(TestCase):
39+
class DjangoAdminTestCase(TestCase):
3940
urls = 'concurrency.tests.urls'
4041

4142
def setUp(self):
42-
super(TestDjangoAdmin, self).setUp()
43+
super(DjangoAdminTestCase, self).setUp()
4344
self.sett = self.settings(INSTALLED_APPS=INSTALLED_APPS,
4445
MIDDLEWARE_CLASSES=global_settings.MIDDLEWARE_CLASSES,
4546
AUTHENTICATION_BACKENDS=global_settings.AUTHENTICATION_BACKENDS,
@@ -48,8 +49,7 @@ def setUp(self):
4849
SOUTH_TESTS_MIGRATE=False,
4950
TEMPLATE_DIRS=(os.path.join(os.path.dirname(__file__), 'templates'),),
5051
# TEMPLATE_LOADERS = ('django.template.loaders.filesystem.Loader',)
51-
52-
)
52+
)
5353
self.sett.enable()
5454
django.core.management._commands = None # reset commands cache
5555
django.core.management.call_command('syncdb', verbosity=0)
@@ -63,11 +63,13 @@ def setUp(self):
6363
self.target1, __ = TestModel1.objects.get_or_create(username='bbb')
6464

6565
def tearDown(self):
66-
super(TestDjangoAdmin, self).tearDown()
66+
super(DjangoAdminTestCase, self).tearDown()
6767
self.sett.disable()
6868
admin.site.unregister(TestModel0)
6969
admin.site.unregister(TestModel1)
7070

71+
class TestDjangoAdmin(DjangoAdminTestCase):
72+
7173
def test_creation(self):
7274
url = reverse('admin:concurrency_testmodel0_add')
7375
data = {'username': u'new_username',
@@ -125,15 +127,19 @@ def test_conflict(self):
125127
url = reverse('admin:concurrency_testmodel1_change', args=[self.target1.pk])
126128
response = self.client.get(url)
127129
self.assertIn('original', response.context, response)
128-
130+
# form = response.context['adminform'].form
131+
rex = re.compile('name="version" value="(\d*):(.[^"]*)"')
132+
m = rex.search(str(response), re.M + re.I)
133+
assert m.group(1) == str(response.context['original'].version)
129134
data = {'username': u'new_username',
130135
'last_name': None,
131-
'version': response.context['adminform'].form['version'].value(),
136+
'version': VersionFieldSigner().sign(m.group(1)),
137+
# 'version': response.context['adminform'].form['version'].value(),
132138
'char_field': None,
133139
'_continue': 1,
134140
'date_field': '2010-09-01'}
135141

136-
self.target1.save() # conflict here
142+
self.target1.save() # create conflict here
137143

138144
response = self.client.post(url, data, follow=True)
139145
self.assertIn('original', response.context, response)
@@ -142,3 +148,25 @@ def test_conflict(self):
142148
self.assertIn('Record Modified',
143149
str(response.context['adminform'].form.errors),
144150
response.context['adminform'].form.errors)
151+
152+
def test_sanity_signer(self):
153+
url = reverse('admin:concurrency_testmodel1_change', args=[self.target1.pk])
154+
response = self.client.get(url)
155+
self.assertIn('original', response.context, response)
156+
rex = re.compile('name="version" value="(\d*):(.[^"]*)"')
157+
158+
m1 = rex.search(str(response), re.M + re.I)
159+
assert m1.group(1) == str(response.context['original'].version)
160+
data = {'username': u'new_username',
161+
'last_name': None,
162+
'version': VersionFieldSigner().sign(m1.group(1)),
163+
'char_field': None,
164+
'_continue': 1,
165+
'date_field': 'esss2010-09-01'}
166+
167+
response = self.client.post(url, data, follow=True)
168+
self.assertIn('original', response.context, response)
169+
self.assertTrue(response.context['adminform'].form.errors,
170+
response.context['adminform'].form.errors)
171+
m2 = rex.search(str(response), re.M + re.I)
172+
self.assertEqual(m1.groups(), m2.groups())

concurrency/tests/forms.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import warnings
2-
from django.core.exceptions import SuspiciousOperation
2+
from django.core.exceptions import SuspiciousOperation, ValidationError
33
from django.forms.models import modelform_factory
44
from django.forms.widgets import HiddenInput, TextInput
55
from django.utils.encoding import smart_str
66
from django.test import TestCase
7+
from concurrency.core import InconsistencyError
78
from concurrency.forms import ConcurrentForm, VersionField, VersionFieldSigner, VersionWidget
89
from concurrency.tests import TestModel0, TestIssue3Model
910
from django.test.testcases import SimpleTestCase
@@ -132,3 +133,19 @@ def test_is_valid(self):
132133
obj.save() # save again simulate concurrent editing
133134
self.assertRaises(ValueError, form.save)
134135

136+
def test_form_is_valid(self):
137+
obj, __ = TestIssue3Model.objects.get_or_create(username='aaa')
138+
Form = modelform_factory(TestIssue3Model, ConcurrentForm)
139+
data = {'username': "a",
140+
'revision': VersionFieldSigner().sign(1)}
141+
form = Form(data)
142+
self.assertRaises(InconsistencyError, form.is_valid)
143+
144+
def test_signing(self):
145+
""" Do not accept version value if adding"""
146+
obj, __ = TestIssue3Model.objects.get_or_create(username='aaa')
147+
Form = modelform_factory(TestIssue3Model, ConcurrentForm)
148+
data = {'username': "a",
149+
'revision': VersionFieldSigner().sign(1)}
150+
form = Form(data)
151+
self.assertRaises(InconsistencyError, form.is_valid)

concurrency/tests/middleware.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
# -*- coding: utf-8 -*-
2+
from django.conf import global_settings
3+
from django.core.urlresolvers import reverse
24
from django.http import HttpRequest
35
from django.test import TestCase
46
from concurrency.core import RecordModifiedError
7+
from concurrency.forms import VersionFieldSigner
58
from concurrency.middleware import ConcurrencyMiddleware
69
from concurrency.tests import TestModel0
10+
from concurrency.tests.contrib_admin import DjangoAdminTestCase
711

812

913
class ConcurrencyMiddlewareTest(TestCase):
@@ -32,3 +36,33 @@ def test_process_exception(self):
3236
request = self._get_request('/')
3337
r = ConcurrencyMiddleware().process_exception(request, RecordModifiedError(target=m))
3438
self.assertEqual(r.status_code, 409)
39+
# self.assertIn('target', r.context)
40+
41+
42+
class CT(DjangoAdminTestCase):
43+
44+
def setUp(self):
45+
DjangoAdminTestCase.setUp(self)
46+
m = ['concurrency.middleware.ConcurrencyMiddleware'] + list(global_settings.MIDDLEWARE_CLASSES)
47+
self.sett = self.settings(MIDDLEWARE_CLASSES= m)
48+
self.sett.enable()
49+
50+
def test_stack(self):
51+
m, __ = TestModel0.objects.get_or_create(username="New", last_name="1")
52+
copy = TestModel0.objects.get(pk=m.pk)
53+
url = reverse('admin:concurrency_testmodel0_change', args=[m.pk])
54+
55+
data = {'username': u'new_username',
56+
'last_name': None,
57+
'version': VersionFieldSigner().sign(m.version),
58+
'char_field': None,
59+
'_continue': 1,
60+
'date_field': '2010-09-01'}
61+
copy.save()
62+
r = self.client.post(url, data, follow=True)
63+
self.assertEqual(r.status_code, 409)
64+
self.assertIn('target', r.context)
65+
self.assertIn('saved', r.context)
66+
self.assertEqual(r.context['saved'].version, copy.version)
67+
self.assertEqual(r.context['target'].version, m.version)
68+
self.assertEqual(r.context['request_path'], url)

concurrency/views.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
from django.template.context import RequestContext
66
from django.views.generic.base import TemplateView
77

8+
# This module is still alpha and should not be used.
89

910
class ConflictResponse(HttpResponse):
1011
status_code = 409
1112

12-
1313
handler409 = 'concurrency.views.conflict'
1414

1515

@@ -41,10 +41,5 @@ def conflict(request, target=None, template_name='409.html'):
4141
ctx = RequestContext(request, {'target': target,
4242
'saved': saved,
4343
'request_path': request.path})
44-
# return http.HttpResponseNotFound(template.render(RequestContext(request, {'request_path': request.path})))
4544
return ConflictResponse(template.render(ctx))
4645

47-
48-
class Http409View(TemplateView):
49-
response_class = ConflictResponse
50-
template_name = None

tox.ini

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[tox]
2+
envlist =
3+
py27_django14
4+
5+
[testenv]
6+
changedir=tests
7+
commands =
8+
django-admin.py test --settings concurren
9+
10+
11+
[testenv:py27_django14]
12+
basepython = python2.7
13+
setenv =
14+
DJANGOVERSION = 1.4.3
15+
deps =
16+
-r{toxinidir}/requirements.pip

0 commit comments

Comments
 (0)