Skip to content

Commit b902bd7

Browse files
committed
- added Widget and forms.VersionField tests
- added ConcurrencyMiddleware
1 parent ccb52bd commit b902bd7

File tree

11 files changed

+167
-79
lines changed

11 files changed

+167
-79
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, 'beta', 9)
5+
VERSION = __version__ = (0, 4, 0, 'rc', 1)
66
__author__ = 'sax'
77

88

concurrency/core.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
from functools import update_wrapper
2-
from django.core.exceptions import ImproperlyConfigured
2+
from django.core.exceptions import ImproperlyConfigured, ValidationError
33
from django.db import DatabaseError, connections, router
44
from django.utils.translation import ugettext as _
55

66
__all__ = []
77

88

9-
class RecordModifiedError(DatabaseError):
9+
class VersionChangedError(ValidationError):
1010
pass
1111

12-
class Http409(Exception):
13-
pass
12+
13+
class RecordModifiedError(DatabaseError):
14+
def __init__(self, *args, **kwargs):
15+
self.target = kwargs.pop('target')
16+
super(RecordModifiedError, self).__init__(*args, **kwargs)
17+
1418

1519
class InconsistencyError(DatabaseError):
1620
pass
@@ -59,7 +63,7 @@ def _select_lock(model_instance, version_value=None):
5963
NOWAIT = connections[alias].features.has_select_for_update_nowait
6064
entry = model_instance.__class__.objects.select_for_update(nowait=NOWAIT).filter(**kwargs)
6165
if not entry:
62-
raise RecordModifiedError(_('Record has been modified'))
66+
raise RecordModifiedError(_('Record has been modified'), target=model_instance)
6367
elif is_versioned:
6468
raise InconsistencyError(_('Version field is set (%s) but record has not `pk`.' % value))
6569

concurrency/forms.py

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ class VersionWidget(HiddenInput):
3939

4040
def render(self, name, value, attrs=None):
4141
ret = super(VersionWidget, self).render(name, value, attrs)
42+
if value is None:
43+
value = ''
4244
return mark_safe("%s<div>%s</div>" % (ret, value))
4345

4446

@@ -56,31 +58,21 @@ def __init__(self, *args, **kwargs):
5658
kwargs.pop('max_value', None)
5759
kwargs['required'] = True
5860
kwargs['initial'] = None
59-
kwargs['widget'] = None
61+
kwargs.setdefault('widget', HiddenInput)
6062
super(VersionField, self).__init__(*args, **kwargs)
6163

62-
def clean(self, value):
63-
try:
64-
# this check is here because some badly written test
65-
if value not in (None, '', 'None'):
66-
return int(self._signer.unsign(value))
67-
return 0
68-
except (BadSignature, ValueError):
69-
raise SuspiciousOperation(_('Version number seems tampered'))
70-
7164
def prepare_value(self, value):
7265
if value:
7366
return self._signer.sign(value)
7467
return None
7568

7669
def to_python(self, value):
77-
if value is None:
78-
return 0
7970
try:
80-
value = int(str(value))
81-
except (ValueError, TypeError):
82-
value = 0
83-
return value
71+
if value not in (None, '', 'None'):
72+
return int(self._signer.unsign(value))
73+
return 0
74+
except (BadSignature, ValueError):
75+
raise SuspiciousOperation(_('Version number seems tampered'))
8476

8577
def widget_attrs(self, widget):
8678
return {}

concurrency/http.py

Lines changed: 0 additions & 9 deletions
This file was deleted.

concurrency/middleware.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
# -*- coding: utf-8 -*-
2-
from concurrency.core import Http409
3-
from concurrency.http import HttpResponseConflict
2+
from django.core.urlresolvers import get_callable
3+
from concurrency.core import RecordModifiedError
4+
from concurrency.views import handler409
45

56

6-
class Http409Middleware(object):
7+
class ConcurrencyMiddleware(object):
78
def process_exception(self, request, exception):
8-
if isinstance(exception, Http409):
9-
return HttpResponseConflict()
9+
if isinstance(exception, RecordModifiedError):
10+
callback = get_callable(handler409)
11+
12+
return callback(request, target=exception.target)

concurrency/tests/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from concurrency.tests.all import *
22
from concurrency.tests.contrib_admin import TestDjangoAdmin
33
from concurrency.tests.forms import *
4-
4+
from concurrency.tests.middleware import *
55

66
try:
77
from concurrency.tests.south_test import *

concurrency/tests/contrib_admin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def test_creation(self):
7777
'_continue': 1,
7878
'date_field': '2010-09-01'}
7979

80-
response = self.client.post(url, data, follow=True)
80+
self.client.post(url, data, follow=True)
8181
self.assertTrue(TestModel0.objects.filter(username='new_username').exists())
8282
self.assertGreater(TestModel0.objects.get(username='new_username').version, 0)
8383

concurrency/tests/forms.py

Lines changed: 55 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
import warnings
12
from django.core.exceptions import SuspiciousOperation
23
from django.forms.models import modelform_factory
34
from django.forms.widgets import HiddenInput, TextInput
45
from django.utils.encoding import smart_str
5-
from django.utils.unittest.case import TestCase
6-
from concurrency.forms import ConcurrentForm, VersionField, VersionFieldSigner
6+
from django.test import TestCase
7+
from concurrency.forms import ConcurrentForm, VersionField, VersionFieldSigner, VersionWidget
78
from concurrency.tests import TestModel0, TestIssue3Model
9+
from django.test.testcases import SimpleTestCase
810

911

1012
class DummySigner():
@@ -14,59 +16,73 @@ def sign(self, value):
1416
def unsign(self, signed_value):
1517
return smart_str(signed_value)
1618

19+
20+
class WidgetTest(TestCase):
21+
def test(self):
22+
w = VersionWidget()
23+
self.assertHTMLEqual(w.render('ver', None),
24+
u'<input name="ver" type="hidden"/><div></div>')
25+
self.assertHTMLEqual(w.render('ver', 100),
26+
u'<input name="ver" type="hidden" value="100"/><div>100</div>')
27+
28+
29+
class FieldTest(SimpleTestCase):
30+
31+
def setUp(self):
32+
self.save_warnings_state()
33+
warnings.filterwarnings('ignore', category=DeprecationWarning,
34+
module='django.core.validators')
35+
36+
def tearDown(self):
37+
self.restore_warnings_state()
38+
39+
def test_with_dummy_signer(self):
40+
f = VersionField(signer=DummySigner())
41+
self.assertEqual(1, f.clean(1))
42+
self.assertEqual(1, f.clean('1'))
43+
self.assertEqual(0, f.clean(None))
44+
self.assertEqual(0, f.clean(''))
45+
self.assertRaisesMessage(SuspiciousOperation,
46+
"Version number seems tampered", f.clean, 'aa:bb')
47+
self.assertRaisesMessage(SuspiciousOperation,
48+
"Version number seems tampered", f.clean, 1.5)
49+
50+
def test(self):
51+
f = VersionField()
52+
self.assertEqual(1, f.clean(VersionFieldSigner().sign(1)))
53+
self.assertEqual(1, f.clean(VersionFieldSigner().sign('1')))
54+
self.assertEqual(0, f.clean(None))
55+
self.assertEqual(0, f.clean(''))
56+
self.assertRaisesMessage(SuspiciousOperation,
57+
"Version number seems tampered", f.clean, '100')
58+
self.assertRaisesMessage(SuspiciousOperation,
59+
"Version number seems tampered",
60+
f.clean,
61+
VersionFieldSigner().sign(1.5))
62+
63+
1764
class ConcurrentFormTest(TestCase):
1865
def test_version(self):
1966
Form = modelform_factory(TestModel0, ConcurrentForm)
2067
form = Form()
2168
self.assertIsInstance(form.fields['version'].widget, HiddenInput)
2269

23-
def test_signer(self):
70+
def test_clean(self):
71+
pass
72+
73+
def test_dummy_signer(self):
2474
obj, __ = TestIssue3Model.objects.get_or_create(username='aaa')
25-
Form = modelform_factory(TestIssue3Model, type('xxx',(ConcurrentForm,), {'revision': VersionField(signer=DummySigner())}))
75+
Form = modelform_factory(TestIssue3Model, type('xxx', (ConcurrentForm,), {'revision': VersionField(signer=DummySigner())}))
2676
data = {'id': 1,
2777
'revision': obj.revision}
2878
form = Form(data, instance=obj)
2979
self.assertTrue(form.is_valid(), form.non_field_errors())
3080

31-
def test_signer2(self):
81+
def test_signer(self):
3282
Form = modelform_factory(TestIssue3Model, ConcurrentForm)
3383
form = Form({'username': 'aaa'})
3484
self.assertTrue(form.is_valid(), form.non_field_errors())
3585

36-
def test_creation_None_version(self):
37-
Form = modelform_factory(TestIssue3Model, type('xxx',(ConcurrentForm,), {}))
38-
data = {'username': 'aaaq',
39-
'last_name': None,
40-
'revision': VersionField().prepare_value(None)}
41-
form = Form(data)
42-
self.assertTrue(form.is_valid())
43-
obj = form.save()
44-
45-
def test_creation_empty_version(self):
46-
Form = modelform_factory(TestIssue3Model, type('xxx',(ConcurrentForm,), {}))
47-
data = {'username': 'aaaq',
48-
'last_name': None,
49-
'revision': VersionField().prepare_value('')}
50-
form = Form(data)
51-
self.assertTrue(form.is_valid())
52-
obj = form.save()
53-
54-
def test_creation_0_version(self):
55-
Form = modelform_factory(TestIssue3Model, type('xxx',(ConcurrentForm,), {}))
56-
data = {'username': 'aaa',
57-
'revision': VersionField().prepare_value(0)}
58-
form = Form(data)
59-
self.assertTrue(form.is_valid())
60-
obj = form.save()
61-
62-
def test_creation_empty_version2(self):
63-
obj, __ = TestIssue3Model.objects.get_or_create(username='aaa')
64-
Form = modelform_factory(TestIssue3Model, type('xxx',(ConcurrentForm,), {'revision': VersionField(signer=DummySigner())}))
65-
data = {'id': 1,
66-
'revision': ''}
67-
form = Form(data, instance=obj)
68-
self.assertTrue(form.is_valid(), form.non_field_errors())
69-
7086
def test_tamperig(self):
7187
obj, __ = TestIssue3Model.objects.get_or_create(username='aaa')
7288
Form = modelform_factory(TestIssue3Model, ConcurrentForm)

concurrency/tests/middleware.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# -*- coding: utf-8 -*-
2+
from django.http import HttpRequest
3+
from django.test import TestCase
4+
from concurrency.core import RecordModifiedError
5+
from concurrency.middleware import ConcurrencyMiddleware
6+
from concurrency.tests import TestModel0
7+
8+
9+
class ConcurrencyMiddlewareTest(TestCase):
10+
def setUp(self):
11+
pass
12+
13+
def tearDown(self):
14+
pass
15+
16+
def _get_request(self, path):
17+
request = HttpRequest()
18+
request.META = {
19+
'SERVER_NAME': 'testserver',
20+
'SERVER_PORT': 80,
21+
}
22+
request.path = request.path_info = "/middleware/%s" % path
23+
return request
24+
25+
def test_process_exception(self):
26+
"""
27+
Tests that RecordModifiedError is handled correctly.
28+
"""
29+
m, __ = TestModel0.objects.get_or_create(username="New", last_name="1")
30+
copy = TestModel0.objects.get(pk=m.pk)
31+
copy.save()
32+
request = self._get_request('/')
33+
r = ConcurrencyMiddleware().process_exception(request, RecordModifiedError(target=m))
34+
self.assertEqual(r.status_code, 409)

concurrency/tests/settings.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import os
2-
31
SITE_ID = 1
42
ROOT_URLCONF = 'demoproject.urls'
53
SECRET_KEY = ';klkj;okj;lkn;lklj;lkj;kjmlliuewhy2ioqwjdkh'

0 commit comments

Comments
 (0)