Skip to content

Commit 10ac3ac

Browse files
committed
Merge branch 'release/2.5'
* release/2.5: lint updates github action config updates tox config bump version fixes check() implementation updates dj41 Update __init__.py test package with tox use markdown for README updates
2 parents 72666d2 + 79b3816 commit 10ac3ac

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+301
-260
lines changed

.github/workflows/tests.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ jobs:
2626
strategy:
2727
fail-fast: false
2828
matrix:
29-
python-version: [ "3.8", "3.9", "3.10"]
30-
django-version: [ "2.2", "3.2", "4.0"]
29+
python-version: [ "3.9", "3.11"]
30+
django-version: [ "3.2", "4.2"]
3131
db-engine: ["pg", "mysql"]
3232
env:
3333
PY_VER: ${{ matrix.python-version}}

.gitignore

+8-14
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,20 @@
1-
˜*
2-
.appveyor.yml
3-
.coverage
1+
~*
2+
.*
3+
!.github
4+
!.gitignore
5+
!.editorconfig
6+
!.pre-commit-config.yaml
47
coverage.xml
5-
~build
6-
.idea
7-
.tox
8-
.DS_Store
9-
.cache
108
notes.txt
11-
docs/build/
9+
build/
10+
dist/
1211
__pycache__
13-
/dist
14-
/build
15-
/MANIFEST
1612
/.eggs
1713
*.egg-info
1814
*.pyc
1915
*.log
2016
*.pot
2117
*.mo
22-
*.pyc
23-
*.egg-info
2418
*.sqlite
2519
.testmondata
2620
Pipfile.lock

CHANGES

+8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
Release 2.5
2+
-----------
3+
* BUG FIX: fixes check() implementation
4+
* removes deprecatd MANUAL_TRIGGERS
5+
* drop support Django 2.x
6+
* drop support python 3.8
7+
8+
19
Release 2.4
210
-----------
311
* add support Django 4

Pipfile

+4-5
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,21 @@ name = "pypi"
66
[packages]
77

88
[dev-packages]
9+
check-manifest = "*"
910
django = "*"
10-
pre-commit = "*"
1111
django-reversion = "*"
1212
django-webtest = "*"
1313
mock = "*"
14+
pre-commit = "*"
15+
psycopg2-binary = "*"
1416
pytest = "*"
1517
pytest-cov = "*"
1618
pytest-django = "*"
1719
pytest-echo = "*"
18-
pytest-pythonpath = "*"
1920
tox = "*"
20-
"psycopg2-binary" = "*"
21-
check-manifest = "*"
21+
twine="*"
2222
sphinx = "*"
2323
sphinx-issues = "*"
24-
twine="*"
2524

2625
[requires]
2726
python_version = "3.8"

README.md

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
Django Concurrency
2+
==================
3+
4+
5+
[![Pypi](https://badge.fury.io/py/django-concurrency.svg)](https://badge.fury.io/py/django-concurrency)
6+
[![coverage](https://codecov.io/github/saxix/django-concurrency/coverage.svg?branch=develop)](https://codecov.io/github/saxix/django-concurrency?branch=develop)
7+
[![Test](https://github.com/saxix/django-concurrency/actions/workflows/tests.yaml/badge.svg)](https://github.com/saxix/django-concurrency/actions/workflows/tests.yaml)
8+
[![Docs](https://readthedocs.org/projects/django-concurrency/badge/?version=stable)](http://django-concurrency.readthedocs.io/en/stable/)
9+
10+
11+
django-concurrency is an [optimistic lock][1] implementation for Django.
12+
13+
It prevents users from doing concurrent editing in Django both from UI and from a
14+
django command.
15+
16+
17+
How it works
18+
------------
19+
20+
```python
21+
22+
from django.db import models
23+
from concurrency.fields import IntegerVersionField
24+
25+
class ConcurrentModel( models.Model ):
26+
version = IntegerVersionField( )
27+
name = models.CharField(max_length=100)
28+
```
29+
30+
Now if you try::
31+
32+
```python
33+
34+
a = ConcurrentModel.objects.get(pk=1)
35+
a.name = '1'
36+
37+
b = ConcurrentModel.objects.get(pk=1)
38+
b.name = '2'
39+
40+
a.save()
41+
b.save()
42+
43+
```
44+
45+
you will get a ``RecordModifiedError`` on ``b.save()``
46+
47+
48+
Similar projects
49+
----------------
50+
51+
Other projects that handle concurrent editing are [django-optimistic-lock][10]
52+
and [django-locking][11] anyway concurrency is "a batteries included" optimistic
53+
lock management system, here some features not available elsewhere:
54+
55+
* can be applied to any model; not only your code (ie. django.contrib.auth.Group)
56+
* handle [list-editable][2] ChangeList. (handle `#11313 <https://code.djangoproject.com/ticket/11313>`_)
57+
* manage concurrency conflicts in admin's actions
58+
* can intercept changes performend out of the django app (ie using pgAdmin, phpMyAdmin, Toads) (using [TriggerVersionField][6])
59+
* can be disabled if needed (see [disable_concurrency][3])
60+
* [ConditionalVersionField][4] to handle complex business rules
61+
62+
63+
64+
Project Links
65+
------------
66+
67+
- Code: https://github.com/saxix/django-concurrency
68+
- Documentation: https://django-concurrency.readthedocs.org/en/latest/
69+
- Issue Tracker: https://github.com/saxix/django-concurrency/issues
70+
- Download Package: http://pypi.python.org/pypi/django-concurrency/
71+
72+
73+
[10]:https://github.com/gavinwahl/django-optimistic-lock
74+
[11]:https://github.com/stdbrouw/django-locking
75+
[1]:http://en.wikipedia.org/wiki/Optimistic_concurrency_control
76+
[2]:https://django-concurrency.readthedocs.org/en/latest/admin.html#list-editable
77+
[3]:https://django-concurrency.readthedocs.org/en/latest/api.html?#disable-concurrency
78+
[4]:https://django-concurrency.readthedocs.org/en/latest/fields.html#conditionalversionfield
79+
[6]:https://django-concurrency.readthedocs.org/en/latest/fields.html#triggerversionfield

README.rst

-114
This file was deleted.

setup.cfg

+8-7
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
combine_as_imports = true
33
default_section = THIRDPARTY
44
include_trailing_comma = true
5-
line_length = 80
6-
known_future_library = future,pies
7-
known_standard_library =
8-
known_third_party = django
9-
known_first_party = sos
10-
multi_line_output = 0
5+
known_tests = pytest,unittest,factory
6+
known_demo = demo
7+
known_django = django
8+
sections = FUTURE,STDLIB,DJANGO,THIRDPARTY,TESTS,FIRSTPARTY,DEMO,LOCALFOLDER
9+
known_first_party = etools_validator
10+
multi_line_output = 3
11+
line_length = 120
1112
balanced_wrapping = true
12-
sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER
13+
order_by_type = false
1314

1415
[flake8]
1516
max-complexity = 12

setup.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ def run_tests(self):
4747
packages=find_packages('src'),
4848
include_package_data=True,
4949
description='Optimistic lock implementation for Django. Prevents users from doing concurrent editing.',
50-
long_description=open('README.rst').read(),
50+
long_description=open('README.md').read(),
51+
long_description_content_type='text/markdown',
5152
license='MIT License',
5253
keywords='django, concurrency, optimistic lock, locking, concurrent editing',
5354
setup_requires=['pytest-runner', ],
@@ -62,6 +63,7 @@ def run_tests(self):
6263
'Framework :: Django :: 3.1',
6364
'Framework :: Django :: 3.2',
6465
'Framework :: Django :: 4.0',
66+
'Framework :: Django :: 4.1',
6567
'Programming Language :: Python :: 3',
6668
'Programming Language :: Python :: 3.8',
6769
'Programming Language :: Python :: 3.9',

src/concurrency/__init__.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
__author__ = 'sax'
2-
default_app_config = 'concurrency.apps.ConcurrencyConfig'
32

4-
VERSION = __version__ = "2.4"
3+
VERSION = __version__ = "2.5"
54
NAME = 'django-concurrency'

src/concurrency/admin.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
from django.contrib.admin import helpers
88
from django.core.checks import Error
99
from django.core.exceptions import ImproperlyConfigured, ValidationError
10+
from django.db import transaction
1011
from django.db.models import Q
11-
from django.forms.formsets import (INITIAL_FORM_COUNT, MAX_NUM_FORM_COUNT,
12-
TOTAL_FORM_COUNT, ManagementForm,)
12+
from django.forms.formsets import INITIAL_FORM_COUNT, ManagementForm, MAX_NUM_FORM_COUNT, TOTAL_FORM_COUNT
1313
from django.forms.models import BaseModelFormSet
1414
from django.http import HttpResponse, HttpResponseRedirect
1515
from django.utils.encoding import force_str
@@ -198,6 +198,7 @@ def _get_conflicts(self, request):
198198
else:
199199
return []
200200

201+
@transaction.atomic()
201202
def save_model(self, request, obj, form, change):
202203
try:
203204
if change:
@@ -266,7 +267,7 @@ class ConcurrentModelAdmin(ConcurrencyActionMixin,
266267
formfield_overrides = {forms.VersionField: {'widget': VersionWidget}}
267268

268269
def check(self, **kwargs):
269-
errors = []
270+
errors = super().check(**kwargs)
270271
if self.fields:
271272
version_field = self.model._concurrencymeta.field
272273
if version_field.name not in self.fields:

src/concurrency/config.py

+2-9
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
1-
import warnings
2-
31
from django.core.exceptions import ImproperlyConfigured
42
from django.test.signals import setting_changed
53
from django.utils.module_loading import import_string
64

75
from .compat import get_callable
86

97
# List Editable Policy
10-
# 0 do not save updated records, save others, show message to the user
11-
# 1 abort whole transaction
8+
# 1 do not save updated records, save others, show message to the user
9+
# 2 abort whole transaction
1210
CONCURRENCY_LIST_EDITABLE_POLICY_SILENT = 1
1311
CONCURRENCY_LIST_EDITABLE_POLICY_ABORT_ALL = 2
1412
CONCURRENCY_POLICY_RAISE = 4
@@ -21,7 +19,6 @@ class AppSettings:
2119
defaults = {
2220
'ENABLED': True,
2321
'AUTO_CREATE_TRIGGERS': True,
24-
'MANUAL_TRIGGERS': False, # deprecated: use AUTO_CREATE_TRIGGERS
2522
'FIELD_SIGNER': 'concurrency.forms.VersionFieldSigner',
2623
'POLICY': CONCURRENCY_LIST_EDITABLE_POLICY_SILENT,
2724
'CALLBACK': 'concurrency.views.callback',
@@ -67,10 +64,6 @@ def _set_attr(self, prefix_name, value):
6764
"{} is not a valid value for `CALLBACK`. It must be a callable or a fullpath to callable. ".format(
6865
value))
6966
self._callback = func
70-
elif name == "MANUAL_TRIGGERS":
71-
warnings.warn("MANUAL_TRIGGERS is deprecated and will be removed in 2.5. Use AUTO_CREATE_TRIGGERS",
72-
category=DeprecationWarning)
73-
self.AUTO_CREATE_TRIGGERS = not value
7467
elif name == "TRIGGERS_FACTORY":
7568
original = dict(value)
7669
for k, v in original.items():

0 commit comments

Comments
 (0)