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