Merge pull request #179 from taigaio/bug/1709/error-on-concurrent-edition
Detecting concurrent edition and failing properly if neededremotes/origin/enhancement/email-actions
commit
911eacd664
|
@ -26,16 +26,20 @@ class OCCResourceMixin(object):
|
|||
Rest Framework resource mixin for resources that need to have concurrent
|
||||
accesses and editions controlled.
|
||||
"""
|
||||
def pre_save(self, obj):
|
||||
current_version = obj.version
|
||||
param_version = self.request.DATA.get('version', None)
|
||||
def _validate_and_update_version(self, obj):
|
||||
current_version = None
|
||||
if obj.id:
|
||||
current_version = type(obj).objects.model.objects.get(id=obj.id).version
|
||||
|
||||
if obj.id is not None and current_version != param_version:
|
||||
param_version = self.request.DATA.get('version', None)
|
||||
if current_version != param_version:
|
||||
raise exc.WrongArguments({"version": "The version doesn't match with the current one"})
|
||||
|
||||
if obj.id:
|
||||
obj.version = models.F('version') + 1
|
||||
|
||||
def pre_save(self, obj):
|
||||
self._validate_and_update_version(obj)
|
||||
super().pre_save(obj)
|
||||
|
||||
def post_save(self, obj, created=False):
|
||||
|
|
|
@ -7,6 +7,9 @@ from taiga.base.utils import json
|
|||
from tests import factories as f
|
||||
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
|
||||
from taiga.projects.votes.services import add_vote
|
||||
from taiga.projects.occ import OCCResourceMixin
|
||||
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
@ -132,6 +135,7 @@ def test_issue_update(client, data):
|
|||
data.project_owner
|
||||
]
|
||||
|
||||
with mock.patch.object(OCCResourceMixin, "_validate_and_update_version") as _validate_and_update_version_mock:
|
||||
issue_data = IssueSerializer(data.public_issue).data
|
||||
issue_data["subject"] = "test"
|
||||
issue_data = json.dumps(issue_data)
|
||||
|
@ -162,6 +166,7 @@ def test_issue_delete(client, data):
|
|||
data.project_member_without_perms,
|
||||
data.project_member_with_perms,
|
||||
]
|
||||
|
||||
results = helper_test_http_method(client, 'delete', public_url, None, users)
|
||||
assert results == [401, 403, 403, 204]
|
||||
results = helper_test_http_method(client, 'delete', private_url1, None, users)
|
||||
|
@ -261,6 +266,7 @@ def test_issue_patch(client, data):
|
|||
data.project_owner
|
||||
]
|
||||
|
||||
with mock.patch.object(OCCResourceMixin, "_validate_and_update_version") as _validate_and_update_version_mock:
|
||||
patch_data = json.dumps({"subject": "test", "version": data.public_issue.version})
|
||||
results = helper_test_http_method(client, 'patch', public_url, patch_data, users)
|
||||
assert results == [401, 403, 403, 200, 200]
|
||||
|
@ -303,7 +309,6 @@ def test_issue_bulk_create(client, data):
|
|||
data.project_owner
|
||||
]
|
||||
|
||||
|
||||
bulk_data = json.dumps({"bulk_issues": "test1\ntest2",
|
||||
"project_id": data.public_issue.project.pk})
|
||||
results = helper_test_http_method(client, 'post', url, bulk_data, users)
|
||||
|
|
|
@ -3,10 +3,13 @@ from django.core.urlresolvers import reverse
|
|||
from taiga.base.utils import json
|
||||
from taiga.projects.tasks.serializers import TaskSerializer
|
||||
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
|
||||
from taiga.projects.occ import OCCResourceMixin
|
||||
|
||||
from tests import factories as f
|
||||
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
|
||||
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
@ -132,6 +135,7 @@ def test_task_update(client, data):
|
|||
data.project_owner
|
||||
]
|
||||
|
||||
with mock.patch.object(OCCResourceMixin, "_validate_and_update_version") as _validate_and_update_version_mock:
|
||||
task_data = TaskSerializer(data.public_task).data
|
||||
task_data["subject"] = "test"
|
||||
task_data = json.dumps(task_data)
|
||||
|
@ -252,6 +256,7 @@ def test_task_patch(client, data):
|
|||
data.project_owner
|
||||
]
|
||||
|
||||
with mock.patch.object(OCCResourceMixin, "_validate_and_update_version") as _validate_and_update_version_mock:
|
||||
patch_data = json.dumps({"subject": "test", "version": data.public_task.version})
|
||||
results = helper_test_http_method(client, 'patch', public_url, patch_data, users)
|
||||
assert results == [401, 403, 403, 200, 200]
|
||||
|
|
|
@ -3,10 +3,13 @@ from django.core.urlresolvers import reverse
|
|||
from taiga.base.utils import json
|
||||
from taiga.projects.userstories.serializers import UserStorySerializer
|
||||
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
|
||||
from taiga.projects.occ import OCCResourceMixin
|
||||
|
||||
from tests import factories as f
|
||||
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
|
||||
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
@ -130,6 +133,7 @@ def test_user_story_update(client, data):
|
|||
data.project_owner
|
||||
]
|
||||
|
||||
with mock.patch.object(OCCResourceMixin, "_validate_and_update_version") as _validate_and_update_version_mock:
|
||||
user_story_data = UserStorySerializer(data.public_user_story).data
|
||||
user_story_data["subject"] = "test"
|
||||
user_story_data = json.dumps(user_story_data)
|
||||
|
@ -235,6 +239,7 @@ def test_user_story_patch(client, data):
|
|||
data.project_owner
|
||||
]
|
||||
|
||||
with mock.patch.object(OCCResourceMixin, "_validate_and_update_version") as _validate_and_update_version_mock:
|
||||
patch_data = json.dumps({"subject": "test", "version": data.public_user_story.version})
|
||||
results = helper_test_http_method(client, 'patch', public_url, patch_data, users)
|
||||
assert results == [401, 403, 403, 200, 200]
|
||||
|
|
|
@ -4,10 +4,13 @@ from taiga.base.utils import json
|
|||
from taiga.projects.wiki.serializers import WikiPageSerializer, WikiLinkSerializer
|
||||
from taiga.projects.wiki.models import WikiPage, WikiLink
|
||||
from taiga.permissions.permissions import MEMBERS_PERMISSIONS, ANON_PERMISSIONS, USER_PERMISSIONS
|
||||
from taiga.projects.occ import OCCResourceMixin
|
||||
|
||||
from tests import factories as f
|
||||
from tests.utils import helper_test_http_method, disconnect_signals, reconnect_signals
|
||||
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
|
@ -121,6 +124,7 @@ def test_wiki_page_update(client, data):
|
|||
data.project_owner
|
||||
]
|
||||
|
||||
with mock.patch.object(OCCResourceMixin, "_validate_and_update_version") as _validate_and_update_version_mock:
|
||||
wiki_page_data = WikiPageSerializer(data.public_wiki_page).data
|
||||
wiki_page_data["content"] = "test"
|
||||
wiki_page_data = json.dumps(wiki_page_data)
|
||||
|
@ -238,6 +242,7 @@ def test_wiki_page_patch(client, data):
|
|||
data.project_owner
|
||||
]
|
||||
|
||||
with mock.patch.object(OCCResourceMixin, "_validate_and_update_version") as _validate_and_update_version_mock:
|
||||
patch_data = json.dumps({"content": "test", "version": data.public_wiki_page.version})
|
||||
results = helper_test_http_method(client, 'patch', public_url, patch_data, users)
|
||||
assert results == [401, 200, 200, 200, 200]
|
||||
|
@ -300,6 +305,7 @@ def test_wiki_link_update(client, data):
|
|||
data.project_owner
|
||||
]
|
||||
|
||||
with mock.patch.object(OCCResourceMixin, "_validate_and_update_version") as _validate_and_update_version_mock:
|
||||
wiki_link_data = WikiLinkSerializer(data.public_wiki_link).data
|
||||
wiki_link_data["title"] = "test"
|
||||
wiki_link_data = json.dumps(wiki_link_data)
|
||||
|
@ -417,6 +423,7 @@ def test_wiki_link_patch(client, data):
|
|||
data.project_owner
|
||||
]
|
||||
|
||||
with mock.patch.object(OCCResourceMixin, "_validate_and_update_version") as _validate_and_update_version_mock:
|
||||
patch_data = json.dumps({"title": "test"})
|
||||
results = helper_test_http_method(client, 'patch', public_url, patch_data, users)
|
||||
assert results == [401, 200, 200, 200, 200]
|
||||
|
|
|
@ -41,7 +41,7 @@ def test_invalid_concurrent_save_for_issue(client):
|
|||
mock_path = "taiga.projects.issues.api.IssueViewSet.pre_conditions_on_save"
|
||||
with patch(mock_path) as m:
|
||||
url = reverse("issues-detail", args=(issue.id,))
|
||||
data = {}
|
||||
data = {"version":9}
|
||||
response = client.patch(url, json.dumps(data), content_type="application/json")
|
||||
assert response.status_code == 400
|
||||
|
||||
|
@ -71,7 +71,7 @@ def test_invalid_concurrent_save_for_wiki_page(client):
|
|||
client.login(user)
|
||||
|
||||
url = reverse("wiki-detail", args=(wiki_page.id,))
|
||||
data = {}
|
||||
data = {"version":9}
|
||||
response = client.patch(url, json.dumps(data), content_type="application/json")
|
||||
assert response.status_code == 400
|
||||
|
||||
|
@ -98,7 +98,7 @@ def test_invalid_concurrent_save_for_us(client):
|
|||
client.login(user)
|
||||
|
||||
url = reverse("userstories-detail", args=(userstory.id,))
|
||||
data = {}
|
||||
data = {"version":9}
|
||||
response = client.patch(url, json.dumps(data), content_type="application/json")
|
||||
assert response.status_code == 400
|
||||
|
||||
|
@ -143,7 +143,7 @@ def test_invalid_concurrent_save_for_task(client):
|
|||
mock_path = "taiga.projects.tasks.api.TaskViewSet.pre_conditions_on_save"
|
||||
with patch(mock_path) as m:
|
||||
url = reverse("tasks-detail", args=(task.id,))
|
||||
data = {}
|
||||
data = {"version":9}
|
||||
response = client.patch(url, json.dumps(data), content_type="application/json")
|
||||
assert response.status_code == 400
|
||||
|
||||
|
|
|
@ -149,7 +149,7 @@ def test_update_userstory_points(client):
|
|||
|
||||
# Api should save successful
|
||||
data = {}
|
||||
data["version"] = usdata["version"]
|
||||
data["version"] = usdata["version"] + 1
|
||||
data["points"] = copy.copy(usdata["points"])
|
||||
data["points"].update({str(role1.pk):points3.pk})
|
||||
|
||||
|
|
Loading…
Reference in New Issue