Skip to content

Commit 2250ab2

Browse files
committed
internal refactoring and deprecations
python 3.2 & django 1.5c1 compatibility
1 parent 553c743 commit 2250ab2

File tree

13 files changed

+159
-42
lines changed

13 files changed

+159
-42
lines changed

.travis.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ services:
55
python:
66
- "2.6"
77
- "2.7"
8+
- "3.2"
89
env:
9-
- DJANGO="Django==1.4.3" DBENGINE=sqlite
10+
# - DJANGO="Django==1.4.3" DBENGINE=sqlite
1011
- DJANGO="Django==1.4.3" DBENGINE=mysql
1112
- DJANGO="Django==1.4.3" DBENGINE=pg
12-
- DJANGO=https://www.djangoproject.com/download/1.5c1/tarball/ DBENGINE=mysql
13+
# - DJANGO=https://www.djangoproject.com/download/1.5c1/tarball/ DBENGINE=mysql
1314
- DJANGO=https://www.djangoproject.com/download/1.5c1/tarball/ DBENGINE=pg
1415
- DJANGO=https://www.djangoproject.com/download/1.5c1/tarball/ DBENGINE=sqlite
1516
install:

concurrency/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,3 @@ def get_git_changeset():
4747
return None
4848
return timestamp.strftime('%Y%m%d%H%M%S')
4949

50-

concurrency/api.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,29 @@
11
# -*- coding: utf-8 -*-
2+
import logging
23
from django.core.exceptions import ImproperlyConfigured
4+
from concurrency.core import _wrap_save, _select_lock, _wrap_model_save
35

4-
from django.utils.translation import gettext as _
5-
from concurrency.core import _wrap_save, _select_lock
6+
__all__ = ['apply_concurrency_check', 'concurrency_check', 'get_revision_of_object']
67

7-
__all__ = ['apply_concurrency_check', 'concurrency_check']
8+
logger = logging.getLogger('concurrency')
9+
10+
11+
def get_revision_of_object(obj):
12+
"""
13+
14+
@param obj:
15+
@return:
16+
"""
17+
revision_field = obj.RevisionMetaInfo.field
18+
value = getattr(obj, revision_field.attname)
19+
return value
20+
21+
22+
def is_changed(obj):
23+
revision_field = obj.RevisionMetaInfo.field
24+
version = getattr(obj, revision_field.attname)
25+
return not obj.__class__.objects.filter(**{obj._meta.pk.name:obj.pk,
26+
revision_field.attname: version}).exists()
827

928

1029
def apply_concurrency_check(model, fieldname, versionclass):
@@ -23,14 +42,14 @@ def apply_concurrency_check(model, fieldname, versionclass):
2342
if hasattr(model, 'RevisionMetaInfo'):
2443
raise ImproperlyConfigured("%s is already under concurrency management" % model)
2544

45+
logger.debug('Applying concurrency check to %s' % model)
46+
2647
ver = versionclass()
2748
ver.contribute_to_class(model, fieldname)
2849
model.RevisionMetaInfo.field = ver
2950

3051
if not model.RevisionMetaInfo.versioned_save:
31-
old_save = getattr(model, 'save')
32-
setattr(model, 'save', _wrap_save(old_save))
33-
model.RevisionMetaInfo.versioned_save = True
52+
_wrap_model_save(model)
3453

3554

3655
def concurrency_check(model_instance, force_insert=False, force_update=False, using=None, **kwargs):

concurrency/core.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
1-
import warnings
2-
from functools import update_wrapper
1+
import logging
2+
from functools import update_wrapper, wraps
33
from django.conf import settings
4-
from django.db import DatabaseError, connections, router
4+
from django.db import connections, router
55
from django.utils.translation import ugettext as _
6+
7+
logger = logging.getLogger('concurrency')
8+
69
from concurrency.exceptions import VersionChangedError, RecordModifiedError, InconsistencyError
10+
from concurrency.utils import deprecated
11+
712

813
__all__ = []
914

10-
def deprecate(target, subst, version):
11-
warnings.warn("`{0}` will be removed in version `{2}`. Please use `{1}`".format(target, subst, version),
12-
category=DeprecationWarning)
1315

16+
@deprecated('concurrency.api.apply_concurrency_check', '0.5')
1417
def apply_concurrency_check(model, fieldname, versionclass):
1518
from concurrency.api import apply_concurrency_check as acc
1619
return acc(model, fieldname, versionclass)
1720

1821

22+
@deprecated('concurrency.api.apply_concurrency_check', '0.5')
1923
def concurrency_check(model_instance, force_insert=False, force_update=False, using=None, **kwargs):
2024
from concurrency.api import concurrency_check as cc
2125
return cc(model_instance, force_insert, force_update, using, **kwargs)
@@ -36,6 +40,14 @@ def _select_lock(model_instance, version_value=None):
3640
raise InconsistencyError(_('Version field is set (%s) but record has not `pk`.' % value))
3741

3842

43+
def _wrap_model_save(model, force=False):
44+
if force or not model.RevisionMetaInfo.versioned_save:
45+
logger.debug('Wrapping save method of %s' % model)
46+
old_save = getattr(model, 'save')
47+
setattr(model, 'save', _wrap_save(old_save))
48+
model.RevisionMetaInfo.versioned_save = True
49+
50+
3951
def _wrap_save(func):
4052
def inner(self, force_insert=False, force_update=False, using=None, **kwargs):
4153
concurrency_check(self, force_insert, force_update, using, **kwargs)
@@ -47,18 +59,11 @@ def inner(self, force_insert=False, force_update=False, using=None, **kwargs):
4759
def _versioned_save(self, force_insert=False, force_update=False, using=None):
4860
if force_insert and force_update:
4961
raise ValueError("Cannot force both insert and updating in model saving.")
50-
concurrency_check(self, force_insert, force_update, using)
62+
if not force_insert:
63+
_select_lock(self)
5164
self.save_base(using=using, force_insert=force_insert, force_update=force_update)
5265

5366

54-
def class_prepared_concurrency_handler(sender, **kwargs):
55-
if hasattr(sender, 'RevisionMetaInfo') and not (sender.RevisionMetaInfo.manually or
56-
sender.RevisionMetaInfo.versioned_save):
57-
old_save = getattr(sender, 'save')
58-
setattr(sender, 'save', _wrap_save(old_save))
59-
sender.RevisionMetaInfo.versioned_save = True
60-
61-
6267
class RevisionMetaInfo:
6368
field = None
6469
versioned_save = False

concurrency/forms.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def clean(self):
2020
try:
2121
_select_lock(self.instance, self.cleaned_data[self.instance.RevisionMetaInfo.field.name])
2222
except RecordModifiedError:
23-
self._update_errors({NON_FIELD_ERRORS: self.error_class([_('Record modified')])})
23+
self._update_errors({NON_FIELD_ERRORS: self.error_class([_('Record Modified')])})
2424

2525
return super(ConcurrentForm, self).clean()
2626

concurrency/models.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
1+
import logging
12
from django.db.models.signals import class_prepared
2-
from concurrency.core import class_prepared_concurrency_handler
3+
from concurrency.core import _wrap_model_save
4+
5+
logger = logging.getLogger('concurrency')
6+
7+
__all__ = []
8+
9+
10+
def class_prepared_concurrency_handler(sender, **kwargs):
11+
if hasattr(sender, 'RevisionMetaInfo') and not (sender.RevisionMetaInfo.manually):
12+
_wrap_model_save(sender)
13+
else:
14+
logger.debug('Skipped concurrency for %s' % sender)
315

416

517
class_prepared.connect(class_prepared_concurrency_handler, dispatch_uid='class_prepared_concurrency_handler')

concurrency/tests/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
from concurrency.tests.forms import *
44
from concurrency.tests.middleware import *
55
from concurrency.tests.conf import *
6+
from concurrency.tests.api import * #NOQA
67

78
try:
89
from concurrency.tests.south_test import *
910
except ImportError:
1011
pass
12+
13+

concurrency/tests/all.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121

2222

2323
logger = logging.getLogger('concurrency.tests')
24-
logger.setLevel(logging.DEBUG)
2524

2625

2726
class ConcurrencyTest0(ConcurrencyTestMixin, TestCase):

concurrency/tests/api.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from django.test import TestCase
2+
from concurrency.api import is_changed, get_revision_of_object
3+
from concurrency.tests import TestModel0
4+
5+
6+
class ConcurrencyTestApi(TestCase):
7+
8+
def test_is_changed(self):
9+
o1 = TestModel0.objects.create()
10+
o2 = TestModel0.objects.get(pk=o1.pk)
11+
self.assertFalse(is_changed(o1))
12+
o1.save()
13+
self.assertTrue(is_changed(o2))
14+
15+
def test_get_revision_of_object(self):
16+
o1 = TestModel0.objects.create()
17+
self.assertEqual(get_revision_of_object(o1), o1.version)
18+

concurrency/tests/models.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
from django.contrib.auth.models import User, Group
22
from django.db import models
33
from django.db.models.base import ModelBase
4-
from concurrency.core import concurrency_check
4+
from concurrency.core import concurrency_check, _wrap_model_save
55
from concurrency.fields import IntegerVersionField, AutoIncVersionField
6+
import logging
7+
8+
logger = logging.getLogger('concurrency.test')
69

710

811
class AbstractConcurrentModel(models.Model):
@@ -185,3 +188,9 @@ class TestIssue3Model(models.Model):
185188
class Meta:
186189
app_label = 'concurrency'
187190

191+
192+
# TODO: investigate why tox requires this. (Maybe depends on the order of the calls of the ``class_prepared`` ?
193+
for model in [TestAbstractModel0, TestModel0, ModelWithCustomSave, TestIssue3Model, AutoIncConcurrentModel,
194+
TestModelWithCustomSave]:
195+
_wrap_model_save(model, True)
196+

0 commit comments

Comments
 (0)