saxix / django-concurrency Goto Github PK
View Code? Open in Web Editor NEWOptimistic lock implementation for Django. Prevents users from doing concurrent editing.
License: MIT License
Optimistic lock implementation for Django. Prevents users from doing concurrent editing.
License: MIT License
For some reason, github shows the broken release 1.3, hides 1.3.1 and 1.3.2, and it looks like 1.4 is not tagged.
Could you tag current and future releases? Thanks!
I am trying to integrate django-concurrency into my django project (v 1.6), but whenever I run schemamigration for south it gives the following error:
! Cannot freeze field 'shop.variation.version'
! (this field has class concurrency.fields.IntegerVersionField)
! South cannot introspect some fields; this is probably because they are custom
! fields. If they worked in 0.6 or below, this is because we have removed the
! models parser (it often broke things).
! To fix this, read http://south.aeracode.org/wiki/MyFieldsDontWork
Is it possible to make the ConcurrencyActionMixin handle custom actions out of the box, without having to override templates with the "identity" tag?
I'm using actions that are working in both modes, concurrency and no concurrency, I'd like to avoid to change and duplicate all my action related template.
Do you think this logic could be done in the Mixin?
Thanks.
Thanks for nice concurency - It is pretty useful. I have one small problem caused probably by my insufficient knowledge. I am using reversion v2.0.12 with concurrency 1.4 on Django 1.8.18.3
I am getting Record Modified error when I try to restore some older version from history.
Please could you help?
Thanks David
Here is my admin code:
from reversion.admin import VersionAdmin
from .models import *
from concurrency.admin import ConcurrentModelAdmin
from concurrency.api import disable_concurrency
class OffersAdmin(VersionAdmin, ConcurrentModelAdmin):
inlines = [
Accomodation,
Food,
]
@disable_concurrency()
def recover_view(self, request, version_id, extra_context=None):
return super(ReversionConcurrentModelAdmin, self).recover_view(request, version_id, extra_context)
From: http://django-concurrency.readthedocs.org/en/latest/api.html#helpers
Is there an easy way to figure out when this is the case? Is it any model with a custom save()?
I'm concerned that there might be cases where I think I'm protected from concurrent editing but I'm not. Kind of like a life insurance policy with loopholes...
All VersionField's has default = 0
For IntegerVersionField it's incorrect and cause the following issue:
Concurrency silently doesn't work for first time object editing after django-concurrency implementation.
When I load one object (with IntegerVersionField value = 0) in two tabs and save 1st, then 2nd, 1st editing will be silently oveddided.
You get the same model instance twice and save each and you get a concurrency error? Why exactly? You haven't changed any fields! Optimistic concurrency means detecting changes to an object at save/commit that overwrite concurrent changes. It generally requires keeping track of mutations of the object somehow or using a shadow object. If this really works like the example it is broken by design.
Hi there,
I'm using Django 1.6.8 and django_concurrency-0.8.1. I've tried to follow the documentation as best as I can, but without examples it's tough to determine if I've set it up correctly in my Admin interface.
I've changed my ConfigurationAdmin class so that it inherits from ConcurrentModelAdmin as the documentation suggests.
class ConfigurationAdmin(ConcurrentModelAdmin):
form = forms.ConfigurationForm
change_list_template = 'admin/configuration/change_list.html'
...
I also changed {{ obj.pk }}
to {{ obj|identity }}
in delete_selected_confirmation.html
as directed in the docs. I haven't changed anything else, and am getting this exception when I go into the Configurations page. (Listing of the Configuration objects which I'm trying to add concurrency to.)
Internal Server Error: /test/common/configuration/
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py", line 137, in get_response
response = response.render()
File "/usr/local/lib/python2.7/dist-packages/django/template/response.py", line 105, in render
self.content = self.rendered_content
File "/usr/local/lib/python2.7/dist-packages/django/template/response.py", line 82, in rendered_content
content = template.render(context)
File "/usr/local/lib/python2.7/dist-packages/django/template/base.py", line 140, in render
return self._render(context)
File "/usr/local/lib/python2.7/dist-packages/django/test/utils.py", line 85, in instrumented_test_render
return self.nodelist.render(context)
File "/usr/local/lib/python2.7/dist-packages/django/template/base.py", line 840, in render
bit = self.render_node(node, context)
File "/usr/local/lib/python2.7/dist-packages/django/template/debug.py", line 78, in render_node
return node.render(context)
File "/usr/local/lib/python2.7/dist-packages/django/template/loader_tags.py", line 123, in render
return compiled_parent._render(context)
File "/usr/local/lib/python2.7/dist-packages/django/test/utils.py", line 85, in instrumented_test_render
return self.nodelist.render(context)
File "/usr/local/lib/python2.7/dist-packages/django/template/base.py", line 840, in render
bit = self.render_node(node, context)
File "/usr/local/lib/python2.7/dist-packages/django/template/debug.py", line 78, in render_node
return node.render(context)
File "/usr/local/lib/python2.7/dist-packages/django/template/loader_tags.py", line 123, in render
return compiled_parent._render(context)
File "/usr/local/lib/python2.7/dist-packages/django/test/utils.py", line 85, in instrumented_test_render
return self.nodelist.render(context)
File "/usr/local/lib/python2.7/dist-packages/django/template/base.py", line 840, in render
bit = self.render_node(node, context)
File "/usr/local/lib/python2.7/dist-packages/django/template/debug.py", line 78, in render_node
return node.render(context)
File "/usr/local/lib/python2.7/dist-packages/django/template/loader_tags.py", line 123, in render
return compiled_parent._render(context)
File "/usr/local/lib/python2.7/dist-packages/django/test/utils.py", line 85, in instrumented_test_render
return self.nodelist.render(context)
File "/usr/local/lib/python2.7/dist-packages/django/template/base.py", line 840, in render
bit = self.render_node(node, context)
File "/usr/local/lib/python2.7/dist-packages/django/template/debug.py", line 78, in render_node
return node.render(context)
File "/usr/local/lib/python2.7/dist-packages/django/template/loader_tags.py", line 62, in render
result = block.nodelist.render(context)
File "/usr/local/lib/python2.7/dist-packages/django/template/base.py", line 840, in render
bit = self.render_node(node, context)
File "/usr/local/lib/python2.7/dist-packages/django/template/debug.py", line 78, in render_node
return node.render(context)
File "/usr/local/lib/python2.7/dist-packages/django/template/loader_tags.py", line 62, in render
result = block.nodelist.render(context)
File "/usr/local/lib/python2.7/dist-packages/django/template/base.py", line 840, in render
bit = self.render_node(node, context)
File "/usr/local/lib/python2.7/dist-packages/django/template/debug.py", line 78, in render_node
return node.render(context)
File "/usr/local/lib/python2.7/dist-packages/django/template/base.py", line 1196, in render
_dict = func(*resolved_args, **resolved_kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/templatetags/admin_list.py", line 288, in result_list
'results': list(results(cl))}
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/templatetags/admin_list.py", line 266, in results
yield ResultList(None, items_for_result(cl, res, None))
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/templatetags/admin_list.py", line 258, in __init__
super(ResultList, self).__init__(*items)
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/templatetags/admin_list.py", line 185, in items_for_result
f, attr, value = lookup_field(field_name, result, cl.model_admin)
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/util.py", line 254, in lookup_field
value = attr(obj)
File "/usr/local/lib/python2.7/dist-packages/concurrency/admin.py", line 37, in action_checkbox
get_revision_of_object(obj))))
File "/usr/local/lib/python2.7/dist-packages/concurrency/api.py", line 23, in get_revision_of_object
return getattr(obj, get_version_fieldname(obj))
File "/usr/local/lib/python2.7/dist-packages/concurrency/core.py", line 22, in get_version_fieldname
return obj._concurrencymeta._field.attname
AttributeError: 'Configuration' object has no attribute '_concurrencymeta'
[21/Nov/2014 20:29:51] "GET /test/common/configuration/ HTTP/1.1" 500 368613
Does anyone know how to resolve this? Thanks!
The current code does not support list_editable Admin config.
Saving a list with list_editable will not trigger any concurrency exception.
This is BTW a long standing bug in Django https://code.djangoproject.com/ticket/11313 (and a few related bugs closed as duplicates too)
So what would be the best approach for this?
Throwing out some thoughts:
Overrride the model admin form to make some checks?
Possibly, on each form in the editable list:
What would be your take on this?
No testcase covers this so far
Incrementing of value at save seems not to work
I'm struggling to understand the cost and benefit of one vs the other. I'm drawn towards AutoIncVersionField as it is more intelligible on it's own but what is the downside?
For a test case like:
class OurTests(TransactionTestCase, ConcurrencyTestMixin):
# ...
Sample output.
Creating test database for alias 'default'...
/Users/evdb/foo/bar/.venv-2.6/lib/python2.6/site-packages/concurrency/utils.py:89: DeprecationWarning: get_revision_of_object is deprecated; use concurrency.api.get_revision_of_object instead
v1 = get_revision_of_object(target)
/Users/evdb/foo/bar/.venv-2.6/lib/python2.6/site-packages/concurrency/utils.py:90: DeprecationWarning: get_revision_of_object is deprecated; use concurrency.api.get_revision_of_object instead
v2 = get_revision_of_object(target_copy)
../Users/evdb/foo/bar/.venv-2.6/lib/python2.6/site-packages/concurrency/utils.py:98: DeprecationWarning: get_revision_of_object is deprecated; use concurrency.api.get_revision_of_object instead
version = get_revision_of_object(target)
.
----------------------------------------------------------------------
Ran 3 tests in 2.482s
I believe that the fix is simply to change the method called in the testing mixin to the not deprecated one.
When list editting, it shows:
Record with pk `1` has been modified and was not updated
1 demo model was changed successfully.
Actually the last message "1 demo model was changed successfully." is not accurate, because the second list editting doesn't not happen(changed successfully) due to the concurrency limit. So in this case, it should be "0 demo model was changed successfully.".
And if a model already has a filed named version, there is a clash possible when using an abstract model with a concurrency version filed named cv and a subclass that has a version field.
Here is the example problem:
Model is:
class Package(models.Model): name = models.CharField(max_length=255) version = models.CharField(max_length=255) concurrency_version = IntegerVersionField()
and the simple code that shows my version field being overwritten:
from test.models import Package p=Package(name='a', version='1.2.3') p.save() Package.objects.all()[0].version u'1354609388041623'
The version filed now is u'1354609388041623' and not u'1.2.3' as expected....
Please refer to: https://github.com/saxix/django-concurrency/blob/develop/concurrency/admin.py#L162
List editting concurrency doens't work if the version field is changed to another name
Collecting django-concurrency==1.0 (from -r requirements.txt (line 41))
Using cached django-concurrency-1.0.tar.gz
Complete output from command python setup.py egg_info:
Traceback (most recent call last):
File "<string>", line 20, in <module>
File "/tmp/pip-build-pMvY07/django-concurrency/setup.py", line 83, in <module>
name=app.NAME,
AttributeError: 'module' object has no attribute 'NAME'
----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-pMvY07/django-concurrency
Trying to upgrade django-concurrency but it's failing. Only django concurrency has this issue, all the other packages are fine.
Hi,
get next version return null when IGNORE_DEFAULT set to False, old_value is null, by right it should be 0
File "...\Anaconda2\lib\site-packages\concurrency\fields.py", line 182, in _do_update
new_version = field._get_next_version(model_instance)
File "...\Anaconda2\lib\site-packages\concurrency\fields.py", line 235, in _get_next_version
return max(int(old_value) + 1, (int(time.time() * 1000000) - OFFSET))
ValueError: invalid literal for int() with base 10: ''
Because the method render_revision_form
does not exist in latest release.
Sorry, I didn't have time to investigate further, but I open the issue now in case someone have a quick answer. In any case, I will come back later with some suggestions.
We tried to use dumpdata
and loaddata
to set up a realistic baseline developer database, and got errors because dumpdata
carries the versions with it.
It would be nice to have a workaround for this. If you're interested, we could look into a pull request, but we'd want to make sure it fits with your overall vision.
I see some related issues like #13 so if you feel this is a closed question from django-concurrency
's perspective, just say so.
I haven't looked into the internals to see if this is difficult or even possible - but I'd like to start a discussion on the subject.
I'd like to be able to raise an exception if a versioned instance is modified without supplying a version value. This would close a loophole where there is some method or API call that I've forgotten to update to handle concurrency checks.
Our use case is a large complex codebase where we want to quickly add baseline protection against concurrent updates - a strict 'fail fast' in any situation where there is a potential for conflicts.
The doc mentions that Django Reversion is supported. Is there any doc to explains how this works?
Hi Stefano,
I see a commit ( a1bcf17 ) that added support for Django 2. But on the readme, it still says
Supported Django versions: 1.8.x, 1.9.x, 1.10.x., 1.11.x
I'm wondering if it's safe to start my new project with Django 2
Thank you!
Hi,
I configured django-concurrency for my app, and it works for the model in admin site, but from my own custom detail template html page which calls view function to update/save model, it does not work, how to configure to support this? thanks.
What is the current advice for handling issues during import etc? Or is this fixed (I'm having trouble in tests and when using django-import-export and this seemed to fit the bill)
How does the use of update_fields on model save work? Does it just work? Is there any caveats?
Nothing obvious in the docs.
After installing django-concurrency, running manage.py compilemessages
raises a ValueError: plural forms expression could be dangerous
because of a bad Plural-Forms header in the https://github.com/saxix/django-concurrency/blob/develop/src/concurrency/locale/en/LC_MESSAGES/django.po#L18.
django-mptt had the same issue django-mptt/django-mptt#156
I have created a pull request for this #83
Thank you!
Hi there,
Versions: Django 1.6.8 and django-concurrency 0.8.1, with MySQL 5.5.40.
I'm developing on a Ubuntu VM. On a clone of this VM, I have added django-concurrency to an object called Configuration on my admin page. After doing so, whenever I use this VM and add more than about 3 new 1-to-many relationships to this Configuration object, I get a TransactionManagementError. This does not happen on the old VM with identical code, and without django-concurrency.
The MySQL logs just show that after a couple of the INSERTs, a rollback command was issued. The database has no exceptions or other information. Here is the exception from Django:
#some INSERTS
Internal Server Error: /test/common/configuration/6/
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py", line 112, in get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/options.py", line 466, in wrapper
return self.admin_site.admin_view(view)(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/utils/decorators.py", line 99, in _wrapped_view
response = view_func(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/views/decorators/cache.py", line 52, in _wrapped_view_func
response = view_func(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/sites.py", line 198, in inner
return view(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/utils/decorators.py", line 29, in _wrapper
return bound_func(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/utils/decorators.py", line 99, in _wrapped_view
response = view_func(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/utils/decorators.py", line 25, in bound_func
return func(self, *args2, **kwargs2)
File "/usr/local/lib/python2.7/dist-packages/django/db/transaction.py", line 371, in inner
return func(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/options.py", line 1267, in change_view
self.log_change(request, new_object, change_message)
File "/usr/local/lib/python2.7/dist-packages/concurrency/admin.py", line 205, in log_change
super(ConcurrencyListEditableMixin, self).log_change(request, object, message)
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/options.py", line 650, in log_change
change_message=message
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/models.py", line 20, in log_action
e.save()
File "/usr/local/lib/python2.7/dist-packages/django/db/models/base.py", line 545, in save
force_update=force_update, update_fields=update_fields)
File "/usr/local/lib/python2.7/dist-packages/django/db/models/base.py", line 573, in save_base
updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
File "/usr/local/lib/python2.7/dist-packages/django/db/models/base.py", line 654, in _save_table
result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
File "/usr/local/lib/python2.7/dist-packages/django/db/models/base.py", line 687, in _do_insert
using=using, raw=raw)
File "/usr/local/lib/python2.7/dist-packages/django/db/models/manager.py", line 232, in _insert
return insert_query(self.model, objs, fields, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/db/models/query.py", line 1514, in insert_query
return query.get_compiler(using=using).execute_sql(return_id)
File "/usr/local/lib/python2.7/dist-packages/django/db/models/sql/compiler.py", line 903, in execute_sql
cursor.execute(sql, params)
File "/usr/local/lib/python2.7/dist-packages/django/db/backends/util.py", line 69, in execute
return super(CursorDebugWrapper, self).execute(sql, params)
File "/usr/local/lib/python2.7/dist-packages/django/db/backends/util.py", line 47, in execute
self.db.validate_no_broken_transaction()
File "/usr/local/lib/python2.7/dist-packages/django/db/backends/__init__.py", line 372, in validate_no_broken_transaction
"An error occurred in the current transaction. You can't "
TransactionManagementError: An error occurred in the current transaction. You can't execute queries until the end of the 'atomic' block.
Internal Server Error: /test/common/configuration/6/
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/django/core/handlers/base.py", line 112, in get_response
response = wrapped_callback(request, *callback_args, **callback_kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/options.py", line 466, in wrapper
return self.admin_site.admin_view(view)(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/utils/decorators.py", line 99, in _wrapped_view
response = view_func(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/views/decorators/cache.py", line 52, in _wrapped_view_func
response = view_func(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/sites.py", line 198, in inner
return view(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/utils/decorators.py", line 29, in _wrapper
return bound_func(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/utils/decorators.py", line 99, in _wrapped_view
response = view_func(request, *args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/utils/decorators.py", line 25, in bound_func
return func(self, *args2, **kwargs2)
File "/usr/local/lib/python2.7/dist-packages/django/db/transaction.py", line 371, in inner
return func(*args, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/options.py", line 1267, in change_view
self.log_change(request, new_object, change_message)
File "/usr/local/lib/python2.7/dist-packages/concurrency/admin.py", line 205, in log_change
super(ConcurrencyListEditableMixin, self).log_change(request, object, message)
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/options.py", line 650, in log_change
change_message=message
File "/usr/local/lib/python2.7/dist-packages/django/contrib/admin/models.py", line 20, in log_action
e.save()
File "/usr/local/lib/python2.7/dist-packages/django/db/models/base.py", line 545, in save
force_update=force_update, update_fields=update_fields)
File "/usr/local/lib/python2.7/dist-packages/django/db/models/base.py", line 573, in save_base
updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)
File "/usr/local/lib/python2.7/dist-packages/django/db/models/base.py", line 654, in _save_table
result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)
File "/usr/local/lib/python2.7/dist-packages/django/db/models/base.py", line 687, in _do_insert
using=using, raw=raw)
File "/usr/local/lib/python2.7/dist-packages/django/db/models/manager.py", line 232, in _insert
return insert_query(self.model, objs, fields, **kwargs)
File "/usr/local/lib/python2.7/dist-packages/django/db/models/query.py", line 1514, in insert_query
return query.get_compiler(using=using).execute_sql(return_id)
File "/usr/local/lib/python2.7/dist-packages/django/db/models/sql/compiler.py", line 903, in execute_sql
cursor.execute(sql, params)
File "/usr/local/lib/python2.7/dist-packages/django/db/backends/util.py", line 69, in execute
return super(CursorDebugWrapper, self).execute(sql, params)
File "/usr/local/lib/python2.7/dist-packages/django/db/backends/util.py", line 47, in execute
self.db.validate_no_broken_transaction()
File "/usr/local/lib/python2.7/dist-packages/django/db/backends/__init__.py", line 372, in validate_no_broken_transaction
"An error occurred in the current transaction. You can't "
TransactionManagementError: An error occurred in the current transaction. You can't execute queries until the end of the 'atomic' block.
[25/Nov/2014 16:33:57] "POST /test/common/configuration/6/ HTTP/1.1" 500 307743
I've added this code to the project so far:
from concurrency.admin import ConcurrentModelAdmin
...
class ConfigurationAdmin(ConcurrentModelAdmin):
form = forms.ConfigurationForm
change_list_template = 'admin/configuration/change_list.html'
...
from concurrency.fields import AutoIncVersionField
class Configuration(models.Model):
"""
Database Model for attaching :class:`Resource` objects to
:class:`Product` objects.
"""
objects = ConfigurationManager()
version = AutoIncVersionField()
#version = models.CharField(max_length=128, verbose_name='config version')
...
{% for obj in queryset %}
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj|identity }}" />
{% endfor %}
Not sure what the problem could be. Any help would be greatly appreciated. Thanks!
I added djano export/import functionalities in the demo project,
this was probably off topic
The changelog states that release 1.3 drops support for Python < 3.3, but the tests at https://travis-ci.org/saxix/django-concurrency/ still include 2.7 as passing. Was the changelog intended to mean versions of Python3 <3.3?
Are there any known issues with using release 1.4 on python 2.7? Thanks!
This may also be related to issue #78
From what I can tell, it may be related to having a TriggerVersionField
in an abstract base class, which causes django to create all the version fields with the same creation_counter
. The creation_counter
is used in __hash__
, which is called via set(_TRIGGERS)
in the create_triggers
function.
I've been having trouble writing a specific test for this, but I did manage to reproduce in another repo
Hi.
Please, add support new style middleware on django 1.10.
Thanks.
Installation fails on Python 3.7.0b1.
% pip install django-concurrency
Collecting django-concurrency==1.4 (from -r requirements/base.txt (line 29))
Using cached django-concurrency-1.4.tar.gz
Complete output from command python setup.py egg_info:
Installed /tmp/easy_install-s5x7gw65/pytest-runner-3.0/.eggs/setuptools_scm-1.15.7-py3.7.egg
zip_safe flag not set; analyzing archive contents...
Traceback (most recent call last):
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/sandbox.py", line 157, in save_modules
yield saved
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/sandbox.py", line 198, in setup_context
yield
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/sandbox.py", line 248, in run_setup
DirectorySandbox(setup_dir).run(runner)
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/sandbox.py", line 278, in run
return func()
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/sandbox.py", line 246, in runner
_execfile(setup_script, ns)
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/sandbox.py", line 47, in _execfile
exec(code, globals, locals)
File "/tmp/easy_install-s5x7gw65/pytest-runner-3.0/setup.py", line 68, in <module>
'License :: OSI Approved :: BSD License',
File "…/pyenv/3.7.0b1/lib/python3.7/distutils/core.py", line 148, in setup
dist.run_commands()
File "…/pyenv/3.7.0b1/lib/python3.7/distutils/dist.py", line 966, in run_commands
self.run_command(cmd)
File "…/pyenv/3.7.0b1/lib/python3.7/distutils/dist.py", line 985, in run_command
cmd_obj.run()
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/command/bdist_egg.py", line 209, in run
os.path.join(archive_root, 'EGG-INFO'), self.zip_safe()
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/command/bdist_egg.py", line 245, in zip_safe
return analyze_egg(self.bdist_dir, self.stubs)
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/command/bdist_egg.py", line 355, in analyze_egg
safe = scan_module(egg_dir, base, name, stubs) and safe
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/command/bdist_egg.py", line 392, in scan_module
code = marshal.load(f)
ValueError: bad marshal data (unknown type code)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "/tmp/pip-build-h66zo67p/django-concurrency/setup.py", line 83, in <module>
platforms=['any'],
File "…/pyenv/3.7.0b1/lib/python3.7/distutils/core.py", line 108, in setup
_setup_distribution = dist = klass(attrs)
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/dist.py", line 315, in __init__
self.fetch_build_eggs(attrs['setup_requires'])
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/dist.py", line 361, in fetch_build_eggs
replace_conflicting=True,
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/pkg_resources/__init__.py", line 850, in resolve
dist = best[req.key] = env.best_match(req, ws, installer)
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/pkg_resources/__init__.py", line 1122, in best_match
return self.obtain(req, installer)
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/pkg_resources/__init__.py", line 1134, in obtain
return installer(requirement)
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/dist.py", line 429, in fetch_build_egg
return cmd.easy_install(req)
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/command/easy_install.py", line 665, in easy_install
return self.install_item(spec, dist.location, tmpdir, deps)
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/command/easy_install.py", line 695, in install_item
dists = self.install_eggs(spec, download, tmpdir)
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/command/easy_install.py", line 876, in install_eggs
return self.build_and_install(setup_script, setup_base)
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/command/easy_install.py", line 1115, in build_and_install
self.run_setup(setup_script, setup_base, args)
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/command/easy_install.py", line 1101, in run_setup
run_setup(setup_script, args)
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/sandbox.py", line 251, in run_setup
raise
File "…/pyenv/3.7.0b1/lib/python3.7/contextlib.py", line 130, in __exit__
self.gen.throw(type, value, traceback)
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/sandbox.py", line 198, in setup_context
yield
File "…/pyenv/3.7.0b1/lib/python3.7/contextlib.py", line 130, in __exit__
self.gen.throw(type, value, traceback)
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/sandbox.py", line 169, in save_modules
saved_exc.resume()
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/sandbox.py", line 144, in resume
six.reraise(type, exc, self._tb)
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/pkg_resources/_vendor/six.py", line 685, in reraise
raise value.with_traceback(tb)
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/sandbox.py", line 157, in save_modules
yield saved
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/sandbox.py", line 198, in setup_context
yield
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/sandbox.py", line 248, in run_setup
DirectorySandbox(setup_dir).run(runner)
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/sandbox.py", line 278, in run
return func()
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/sandbox.py", line 246, in runner
_execfile(setup_script, ns)
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/sandbox.py", line 47, in _execfile
exec(code, globals, locals)
File "/tmp/easy_install-s5x7gw65/pytest-runner-3.0/setup.py", line 68, in <module>
'License :: OSI Approved :: BSD License',
File "…/pyenv/3.7.0b1/lib/python3.7/distutils/core.py", line 148, in setup
dist.run_commands()
File "…/pyenv/3.7.0b1/lib/python3.7/distutils/dist.py", line 966, in run_commands
self.run_command(cmd)
File "…/pyenv/3.7.0b1/lib/python3.7/distutils/dist.py", line 985, in run_command
cmd_obj.run()
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/command/bdist_egg.py", line 209, in run
os.path.join(archive_root, 'EGG-INFO'), self.zip_safe()
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/command/bdist_egg.py", line 245, in zip_safe
return analyze_egg(self.bdist_dir, self.stubs)
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/command/bdist_egg.py", line 355, in analyze_egg
safe = scan_module(egg_dir, base, name, stubs) and safe
File "…/pyenv/3.7.0b1/envs/tmp-3.7.0b1-project-1FLUQD/lib/python3.7/site-packages/setuptools/command/bdist_egg.py", line 392, in scan_module
code = marshal.load(f)
ValueError: bad marshal data (unknown type code)
This is a subtle heisenbug.
In short, if you have a model
class MyModel(models.Model):
version = IntegerVersionField()
def save(*args, **kwargs):
bla-bla-bla
then sometimes save is wrapped by django-concurrency and all works fine, but sometimes save is not wrapped, and there is effectively no concurrency check.
More precisely, sometimes depends on hash seed of python interpreter. I.e. in my setup all is working if PYTHONHASHSEED=42 and if PYTHONHASHSEED=2 then concurrency is broken.
If a model doesn't define a custom save method, then there is no such weird behaviour.
I think I've spotted the cause of the bug. The problem is that save gets wrapped inside contribute_to_class
https://github.com/saxix/django-concurrency/blob/develop/concurrency/fields.py#L67.
And contribute_to_class
is called by Model metaclass here https://github.com/django/django/blob/master/django/db/models/base.py#L155.
Note that add_to_class
(https://github.com/django/django/blob/master/django/db/models/base.py#L285) calls contribute_to_class
if the argument has this method, or just setattr
otherwise.
And add_to_class
is called for all attributes of the model, including version
field and save
method, if model defines one. So if add_to_class
is called on save
after add_to_class
is called on version
, wrapped model save gets overwritten with plain save
. The order of version
and save
depends on iteration order of attrs
dict, which depends on PYTHONHASHSEED.
The proposed fix is to rewrite contribute_to_class
in such a way that wrapping of save
method is bound to class_prepared
signal. Something similar is done in django simple history here: https://github.com/treyhunner/django-simple-history/blob/master/simple_history/models.py#L44
It would be great to have a basic admin ui to resolve concurrency exceptions....
Something that could show:
Hi,
Is there anything special to do to get the triggers to be created when running the tests?
When I run the tests on my project, I get this
WARNINGS:
?: (concurrency.W001) Missed trigger for field beyond.User.version
When I check the test database, the triggers are never created.
I am using django 1.11 and I have tried with the latest commit: 2c92198
Thanks
in https://github.com/saxix/django-concurrency/blob/develop/concurrency/core.py#L48
model_instance.__class__.objects
is used to get a manager. However this has two drawbacks:
objects
manager for a particular model (https://docs.djangoproject.com/en/dev/topics/db/managers/#manager-names)objects
can filter out some rows(for example, in soft delete scenario)I suggest using model_instance.class._base_manager or model_instance.class._default_manager but I'm not sure because these are underscored private attributes...
The variable _concurrency_list_editable_errors should not be stored on the ModelAdmin instance as it's shared by all the Users. I would suggest to store it on the "request" instead as it's unique for each User and each Request.
Also, initializing it in the changelist_view() method is not great as it requires to go through that view before the other views can work. Note that moving the logic for _concurrency_list_editable_errors to the "request" and initializing it in a better "entry point" method will solve this issue too.
Or not? I want use it with django 1.11.
Hello
When using the trigger-based field (TriggerVersionField
), I get a django.db.utils.OperationalError: server closed the connection unexpectedly
error after the first RecordModifiedError
was raised.
I do not have this issue using the AutoIncVersionField
field but I cannot reproduce this issue in the django shell either so I am not sure what is causing it.
Could there be an issue in the trigger? Have you ever had this issue? Any idea?
Thanks
django==1.9.1
django-concurrency==1.3.1
PostgreSQL 9.5.3
Expected RecordModifiedError
on user.save()
22:56:15 worker3.1 | File "/var/app/venv/lib/python3.5/site-packages/concurrency/fields.py", line 208, in _do_update
22:56:15 worker3.1 | updated = conf._callback(model_instance)
22:56:15 worker3.1 | File "/var/app/venv/lib/python3.5/site-packages/concurrency/views.py", line 19, in callback
22:56:15 worker3.1 | raise RecordModifiedError(_('Record has been modified'), target=target)
22:56:15 worker3.1 | concurrency.exceptions.RecordModifiedError: Record has been modified
22:56:15 worker3.1 |
Then a User.objects.get(id=id)
raises:
22:57:12 worker2.1 | File "/var/app/venv/lib/python3.5/site-packages/django/db/models/manager.py", line 122, in manager_method
22:57:12 worker2.1 | return getattr(self.get_queryset(), name)(*args, **kwargs)
22:57:12 worker2.1 | File "/var/app/venv/lib/python3.5/site-packages/django/db/models/query.py", line 381, in get
22:57:12 worker2.1 | num = len(clone)
22:57:12 worker2.1 | File "/var/app/venv/lib/python3.5/site-packages/django/db/models/query.py", line 240, in __len__
22:57:12 worker2.1 | self._fetch_all()
22:57:12 worker2.1 | File "/var/app/venv/lib/python3.5/site-packages/django/db/models/query.py", line 1074, in _fetch_all
22:57:12 worker2.1 | self._result_cache = list(self.iterator())
22:57:12 worker2.1 | File "/var/app/venv/lib/python3.5/site-packages/django/db/models/query.py", line 52, in __iter__
22:57:12 worker2.1 | results = compiler.execute_sql()
22:57:12 worker2.1 | File "/var/app/venv/lib/python3.5/site-packages/django/db/models/sql/compiler.py", line 848, in execute_sql
22:57:12 worker2.1 | cursor.execute(sql, params)
22:57:12 worker2.1 | File "/var/app/venv/lib/python3.5/site-packages/django/db/backends/utils.py", line 79, in execute
22:57:12 worker2.1 | return super(CursorDebugWrapper, self).execute(sql, params)
22:57:12 worker2.1 | File "/var/app/venv/lib/python3.5/site-packages/django/db/backends/utils.py", line 64, in execute
22:57:12 worker2.1 | return self.cursor.execute(sql, params)
22:57:12 worker2.1 | File "/var/app/venv/lib/python3.5/site-packages/django/db/utils.py", line 95, in __exit__
22:57:12 worker2.1 | six.reraise(dj_exc_type, dj_exc_value, traceback)
22:57:12 worker2.1 | File "/var/app/venv/lib/python3.5/site-packages/django/utils/six.py", line 685, in reraise
22:57:12 worker2.1 | raise value.with_traceback(tb)
22:57:12 worker2.1 | File "/var/app/venv/lib/python3.5/site-packages/django/db/backends/utils.py", line 64, in execute
22:57:12 worker2.1 | return self.cursor.execute(sql, params)
22:57:12 worker2.1 | django.db.utils.OperationalError: server closed the connection unexpectedly
22:57:12 worker2.1 | This probably means the server terminated abnormally
22:57:12 worker2.1 | before or while processing the request.
22:57:12 worker2.1 |
Need update to support Django1.8:
concurrency\forms.py:7: RemovedInDjango19Warning: django.utils.importlib will be removed in Django 1.9.
from django.utils.importlib import import_module
concurrency\utils.py:7: RemovedInDjango19Warning: The utilities in django.db.models.loading are deprecated in favor of the new application loading system.
from django.db.models.loading import get_models, get_apps
Hi Saxix,
just wanted to add a version lock field to an existing model with several existing migrations and got.
... models.py
behind the class defintion.
from concurrency import fields
lock_version = fields.AutoIncVersionField()
lock_version.contribute_to_class(Account, 'lock_version')
manage.py schemamigration company --auto
produces this stacktrace
! Cannot freeze field 'company.account.lock_version'
! (this field has class concurrency.fields.AutoIncVersionField)
! South cannot introspect some fields; this is probably because they are custom
! fields. If they worked in 0.6 or below, this is because we have removed the
! models parser (it often broke things).
! To fix this, read http://south.aeracode.org/wiki/MyFieldsDontWork
The bot created this issue to inform you that pyup.io has been set up on this repo.
Once you have closed it, the bot will open pull requests for updates as soon as they are available.
Such field may work like this:
fields = self.get_ool_fields()
exclude = self.get_ool_exclusions()
data = model_to_dict(self, fields=fields, exclude=exclude, filter_not_editable=False)
# Resolve nested resources
if isinstance(fields, (list, tuple)):
for source in fields:
try:
value = self
for component in source.split('.'):
if value is None:
break
value = get_component(value, component)
except ObjectDoesNotExist:
continue
if is_simple_callable(getattr(value, 'all', None)):
value = [item.pk for item in value.all()]
data[source] = value
data = OrderedDict(sorted(data.items()))
version = hashlib.sha1(force_text(data)).hexdigest()
so based on model settings it's generate hash for a set of fields values.
this may be very handy in case one want to bump a version only if business critical fields have changed (helps when model updates often)
Suppose we have a model,
class User(models.Model):
version = AutoIncVersionField()
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
name = models.CharField(max_length=255)
If we create the object using create, we are getting the expected version value(which is 1).
User.objects.create(name='name')
But instead of that if we create the model using the initializer and create it using the save() method. The version is 2.
user = User(name='name')
user.save()
https://github.com/saxix/django-concurrency/blob/master/docs/index.rst
The links point to iadmin?
When i add a new "Demo models" in "Demoapp", the following error happens:
MultiValueDictKeyError at /admin/demoapp/demomodel/add/
"Key u'_concurrency_version_None' not found in <QueryDict: {u'char': [u'abc'], u'integer': [u'2'], u'csrfmiddlewaretoken': [u'fMPewSuw05AdKheUcbSjrah0myrrgdEh'], u'version': [u''], u'_save': [u'Save']}>"
Request Method: POST
Request URL: http://127.0.0.1:8000/admin/demoapp/demomodel/add/
Django Version: 1.5.1
Exception Type: MultiValueDictKeyError
Exception Value:
"Key u'_concurrency_version_None' not found in <QueryDict: {u'char': [u'abc'], u'integer': [u'2'], u'csrfmiddlewaretoken': [u'fMPewSuw05AdKheUcbSjrah0myrrgdEh'], u'version': [u''], u'_save': [u'Save']}>"
Exception Location: C:\Python27\lib\site-packages\django\utils\datastructures.py in getitem, line 295
Python Executable: C:\Python27\python.exe
Python Version: 2.7.0
Python Path:
['E:\gitpro\django-concurrency\demo',
'C:\Windows\system32\python27.zip',
'C:\Python27\DLLs',
'C:\Python27\lib',
'C:\Python27\lib\plat-win',
'C:\Python27\lib\lib-tk',
'C:\Python27',
'C:\Python27\lib\site-packages',
If you use ConcurrentModelAdmin , and you manually set fieldsets or fields in admin, you must ensure the version field gets added to the admin. Otherwise concurrency checks will not be enforced.
This small detail took me almost 2 hours to discover. 1 line of documentation would protect future users from this.
Btw: thank you for a very cool library 👍
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.