瀏覽代碼

Merge pull request #555 from inkhey/fix/535/Do_not_create_empty_revision

Bastien Sevajol 6 年之前
父節點
當前提交
82d464775d
沒有帳戶連結到提交者的電子郵件

+ 58 - 44
tracim/tracim/controllers/content.py 查看文件

@@ -174,7 +174,7 @@ class UserWorkspaceFolderFileRestController(TIMWorkspaceContentRestController):
174 174
 
175 175
     @property
176 176
     def _err_url(self):
177
-        return tg.url('/dashboard/workspaces/{}/folders/{}/file/{}')
177
+        return tg.url('/workspaces/{}/folders/{}/files/{}')
178 178
 
179 179
     @property
180 180
     def _item_type(self):
@@ -376,19 +376,13 @@ class UserWorkspaceFolderFileRestController(TIMWorkspaceContentRestController):
376 376
         try:
377 377
             api = ContentApi(tmpl_context.current_user)
378 378
             item = api.get_one(int(item_id), self._item_type, workspace)
379
-            label_changed = False
380
-            if label is not None and label != item.label:
381
-                label_changed = True
382
-
383
-            if label is None:
384
-                label = ''
385
-
386
-            # TODO - D.A. - 2015-03-19
387
-            # refactor this method in order to make code easier to understand
388 379
 
389 380
             with new_revision(item):
381
+                if label:
382
+                    # This case is the default "file title and description
383
+                    # update" In this case the file itself is not revisionned
390 384
 
391
-                if (comment and label) or (not comment and label_changed):
385
+                    # Update description and label
392 386
                     updated_item = api.update_content(
393 387
                         item, label if label else item.label,
394 388
                         comment if comment else ''
@@ -401,31 +395,21 @@ class UserWorkspaceFolderFileRestController(TIMWorkspaceContentRestController):
401 395
                         return render_invalid_integrity_chosen_path(
402 396
                             updated_item.get_label_as_file(),
403 397
                         )
404
-
405 398
                     api.save(updated_item, ActionDescription.EDITION)
406
-
407
-                    # This case is the default "file title and description
408
-                    # update" In this case the file itself is not revisionned
409
-
410 399
                 else:
411 400
                     # So, now we may have a comment and/or a file revision
412
-                    if comment and '' == label:
413
-                        comment_item = api.create_comment(workspace,
414
-                                                          item, comment,
415
-                                                          do_save=False)
416
-
417
-                        if not isinstance(file_data, FieldStorage):
418
-                            api.save(comment_item, ActionDescription.COMMENT)
419
-                        else:
420
-                            # The notification is only sent
421
-                            # if the file is NOT updated
422
-                            #
423
-                            # If the file is also updated,
424
-                            # then a 'file revision' notification will be sent.
425
-                            api.save(comment_item,
426
-                                     ActionDescription.COMMENT,
427
-                                     do_notify=False)
401
+                    comment_item = None
402
+                    file_revision = None
428 403
 
404
+                    # INFO - G.M - 20/03/2018 - Add new comment
405
+                    if comment:
406
+                        comment_item = api.create_comment(
407
+                            workspace,
408
+                            item,
409
+                            comment,
410
+                            do_save=False
411
+                        )
412
+                    # INFO - G.M - 20/03/2018 - Add new file-revision
429 413
                     if isinstance(file_data, FieldStorage):
430 414
                         api.update_file_data(item,
431 415
                                              file_data.filename,
@@ -440,8 +424,20 @@ class UserWorkspaceFolderFileRestController(TIMWorkspaceContentRestController):
440 424
                             return render_invalid_integrity_chosen_path(
441 425
                                 item.get_label_as_file(),
442 426
                             )
443
-
427
+                        file_revision = True
428
+
429
+                    # INFO - G.M - 20/03/2018 - Save revision/comment
430
+                    if comment_item and file_revision:
431
+                        api.save(
432
+                            comment_item,
433
+                            ActionDescription.COMMENT,
434
+                            do_notify= False
435
+                        )
436
+                        api.save(item, ActionDescription.REVISION)
437
+                    elif file_revision:
444 438
                         api.save(item, ActionDescription.REVISION)
439
+                    elif comment_item:
440
+                        api.save(comment_item, ActionDescription.COMMENT)
445 441
 
446 442
             msg = _('{} updated').format(self._item_type_label)
447 443
             tg.flash(msg, CST.STATUS_OK)
@@ -449,6 +445,14 @@ class UserWorkspaceFolderFileRestController(TIMWorkspaceContentRestController):
449 445
                                              tmpl_context.folder_id,
450 446
                                              item.content_id))
451 447
 
448
+        except SameValueError:
449
+            not_updated = '{} not updated: the content did not change'
450
+            msg = _(not_updated).format(self._item_type_label)
451
+            tg.flash(msg, CST.STATUS_WARNING)
452
+            tg.redirect(self._err_url.format(tmpl_context.workspace_id,
453
+                                             tmpl_context.folder_id,
454
+                                             item_id))
455
+
452 456
         except ValueError as e:
453 457
             error = '{} not updated - error: {}'
454 458
             msg = _(error).format(self._item_type_label,
@@ -459,6 +463,7 @@ class UserWorkspaceFolderFileRestController(TIMWorkspaceContentRestController):
459 463
                                              item_id))
460 464
 
461 465
 
466
+
462 467
 class UserWorkspaceFolderPageRestController(TIMWorkspaceContentRestController):
463 468
     """
464 469
     manage a path like this: /workspaces/1/folders/XXX/pages/4
@@ -577,9 +582,11 @@ class UserWorkspaceFolderPageRestController(TIMWorkspaceContentRestController):
577 582
 
578 583
         tg.flash(_('Page created'), CST.STATUS_OK)
579 584
         redirect = '/workspaces/{}/folders/{}/pages/{}'
580
-        tg.redirect(tg.url(redirect).format(tmpl_context.workspace_id,
581
-                                            tmpl_context.folder_id,
582
-                                            page.content_id))
585
+        tg.redirect(tg.url(redirect).format(
586
+            tmpl_context.workspace_id,
587
+            tmpl_context.folder_id,
588
+            page.content_id)
589
+        )
583 590
 
584 591
     @tg.require(current_user_is_contributor())
585 592
     @tg.expose()
@@ -606,21 +613,28 @@ class UserWorkspaceFolderPageRestController(TIMWorkspaceContentRestController):
606 613
             tg.redirect(self._std_url.format(tmpl_context.workspace_id,
607 614
                                              tmpl_context.folder_id,
608 615
                                              item.content_id))
609
-        except SameValueError as e:
616
+        except SameValueError:
610 617
             not_updated = '{} not updated: the content did not change'
611 618
             msg = _(not_updated).format(self._item_type_label)
612 619
             tg.flash(msg, CST.STATUS_WARNING)
613
-            tg.redirect(self._err_url.format(tmpl_context.workspace_id,
614
-                                             tmpl_context.folder_id,
615
-                                             item_id))
620
+            tg.redirect(
621
+                self._err_url.format(
622
+                    tmpl_context.workspace_id,
623
+                    tmpl_context.folder_id,
624
+                    item_id
625
+                )
626
+            )
616 627
         except ValueError as e:
617 628
             not_updated = '{} not updated - error: {}'
618 629
             msg = _(not_updated).format(self._item_type_label, str(e))
619 630
             tg.flash(msg, CST.STATUS_ERROR)
620
-            tg.redirect(self._err_url.format(tmpl_context.workspace_id,
621
-                                             tmpl_context.folder_id,
622
-                                             item_id))
623
-
631
+            tg.redirect(
632
+                self._err_url.format(
633
+                    tmpl_context.workspace_id,
634
+                    tmpl_context.folder_id,
635
+                    item_id
636
+                )
637
+            )
624 638
 
625 639
 class UserWorkspaceFolderThreadRestController(TIMWorkspaceContentRestController):
626 640
     """

+ 6 - 1
tracim/tracim/lib/content.py 查看文件

@@ -920,7 +920,9 @@ class ContentApi(object):
920 920
 
921 921
     def update_content(self, item: Content, new_label: str, new_content: str=None) -> Content:
922 922
         if item.label==new_label and item.description==new_content:
923
-            raise SameValueError(_('The content did not changed'))
923
+            # TODO - G.M - 20-03-2018 - Fix internatization for webdav access.
924
+            # Internatization disabled in libcontent for now.
925
+            raise SameValueError('The content did not changed')
924 926
         item.owner = self._user
925 927
         item.label = new_label
926 928
         item.description = new_content if new_content else item.description # TODO: convert urls into links
@@ -928,6 +930,9 @@ class ContentApi(object):
928 930
         return item
929 931
 
930 932
     def update_file_data(self, item: Content, new_filename: str, new_mimetype: str, new_content: bytes) -> Content:
933
+        if new_mimetype == item.file_mimetype and \
934
+                new_content == item.depot_file.file.read():
935
+            raise SameValueError('The content did not changed')
931 936
         item.owner = self._user
932 937
         item.file_name = new_filename
933 938
         item.file_mimetype = new_mimetype

+ 31 - 7
tracim/tracim/lib/webdav/__init__.py 查看文件

@@ -5,11 +5,15 @@ from wsgidav import util
5 5
 from wsgidav import compat
6 6
 
7 7
 from tracim.lib.content import ContentApi
8
+from tracim.lib.utils import SameValueError
9
+from tracim.lib.base import logger
8 10
 from tracim.model import new_revision
9 11
 from tracim.model.data import ActionDescription
10 12
 from tracim.model.data import ContentType
11 13
 from tracim.model.data import Content
12 14
 from tracim.model.data import Workspace
15
+from wsgidav.dav_error import DAVError
16
+from wsgidav.dav_error import HTTP_FORBIDDEN
13 17
 
14 18
 
15 19
 class HistoryType(object):
@@ -96,12 +100,33 @@ class FakeFileStream(object):
96 100
 
97 101
         self._file_stream.seek(0)
98 102
 
99
-        if self._content is None:
100
-            self.create_file()
101
-        else:
102
-            self.update_file()
103
-
104
-        transaction.commit()
103
+        try:
104
+            if self._content is None:
105
+                self.create_file()
106
+            else:
107
+                self.update_file()
108
+            transaction.commit()
109
+        except SameValueError:
110
+            # INFO - G.M - 21-03-2018 - Do nothing, file as not change.
111
+            msg = 'File {filename} modified through webdav did not change, transaction aborted.'  # nopep8
112
+            logger.debug(
113
+                self,
114
+                msg.format(filename=self._file_name)
115
+            )
116
+            transaction.abort()
117
+            transaction.begin()
118
+        except ValueError as e:
119
+            msg = 'File {filename} modified through webdav can\'t be updated: {exception}'  # nopep8
120
+            logger.debug(
121
+                self,
122
+                msg.format(
123
+                    filename=self._file_name,
124
+                    e=e.__str__,
125
+                )
126
+            )
127
+            transaction.abort()
128
+            transaction.begin()
129
+            raise DAVError(HTTP_FORBIDDEN)
105 130
 
106 131
     def create_file(self):
107 132
         """
@@ -138,5 +163,4 @@ class FakeFileStream(object):
138 163
                 util.guessMimeType(self._content.file_name),
139 164
                 self._file_stream.read()
140 165
             )
141
-
142 166
             self._api.save(self._content, ActionDescription.REVISION)

+ 9 - 1
tracim/tracim/model/__init__.py 查看文件

@@ -8,7 +8,8 @@ from sqlalchemy.orm.unitofwork import UOWTransaction
8 8
 from zope.sqlalchemy import ZopeTransactionExtension
9 9
 
10 10
 from tracim.lib.exception import ContentRevisionUpdateError, ContentRevisionDeleteError
11
-
11
+from tracim.lib.utils import SameValueError
12
+import transaction
12 13
 
13 14
 class RevisionsIntegrity(object):
14 15
     """
@@ -136,5 +137,12 @@ def new_revision(
136 137
                 content.new_revision()
137 138
             RevisionsIntegrity.add_to_updatable(content.revision)
138 139
             yield content
140
+        except SameValueError or ValueError as e:
141
+            # INFO - 20-03-2018 - renew transaction when error happened
142
+            # This avoid bad session data like new "temporary" revision
143
+            # to be add when problem happen.
144
+            transaction.abort()
145
+            transaction.begin()
146
+            raise e
139 147
         finally:
140 148
             RevisionsIntegrity.remove_from_updatable(content.revision)

+ 116 - 0
tracim/tracim/tests/library/test_content_api.py 查看文件

@@ -11,6 +11,7 @@ from tracim.lib.content import compare_content_for_sorting_by_type_and_name
11 11
 from tracim.lib.content import ContentApi
12 12
 from tracim.lib.group import GroupApi
13 13
 from tracim.lib.user import UserApi
14
+from tracim.lib.utils import SameValueError
14 15
 from tracim.lib.workspace import RoleApi
15 16
 from tracim.lib.workspace import WorkspaceApi
16 17
 from tracim.model import DBSession, new_revision, User
@@ -829,6 +830,60 @@ class TestContentApi(BaseTest, TestStandard):
829 830
         eq_('new content', updated.description)
830 831
         eq_(ActionDescription.EDITION, updated.revision_type)
831 832
 
833
+    @raises(SameValueError)
834
+    def test_update_no_change(self):
835
+        uapi = UserApi(None)
836
+        groups = [
837
+            GroupApi(None).get_one(Group.TIM_USER),
838
+            GroupApi(None).get_one(Group.TIM_MANAGER),
839
+            GroupApi(None).get_one(Group.TIM_ADMIN)
840
+        ]
841
+
842
+        user1 = uapi.create_user(
843
+            email='this.is@user',
844
+            groups=groups,
845
+            save_now=True,
846
+        )
847
+
848
+        workspace = WorkspaceApi(user1).create_workspace(
849
+            'test workspace',
850
+            save_now=True
851
+        )
852
+
853
+        user2 = uapi.create_user()
854
+        user2.email = 'this.is@another.user'
855
+        uapi.save(user2)
856
+
857
+        RoleApi(user1).create_one(
858
+            user2,
859
+            workspace,
860
+            UserRoleInWorkspace.CONTENT_MANAGER,
861
+            with_notif=False,
862
+            flush=True
863
+        )
864
+        api = ContentApi(user1)
865
+        with DBSession.no_autoflush:
866
+            page = api.create(
867
+                content_type=ContentType.Page,
868
+                workspace=workspace,
869
+                label="same_content",
870
+                do_save=False
871
+            )
872
+            page.description = "Same_content_here"
873
+        api.save(page, ActionDescription.CREATION, do_notify=True)
874
+        transaction.commit()
875
+
876
+        api2 = ContentApi(user2)
877
+        content2 = api2.get_one(page.content_id, ContentType.Any, workspace)
878
+        with new_revision(content2):
879
+            api2.update_content(
880
+                item=content2,
881
+                new_label='same_content',
882
+                new_content='Same_content_here'
883
+            )
884
+        api2.save(content2)
885
+        transaction.commit()
886
+
832 887
     def test_update_file_data(self):
833 888
         uapi = UserApi(None)
834 889
         groups = [GroupApi(None).get_one(Group.TIM_USER),
@@ -896,6 +951,67 @@ class TestContentApi(BaseTest, TestStandard):
896 951
         eq_(b'<html>hello world</html>', updated.depot_file.file.read())
897 952
         eq_(ActionDescription.REVISION, updated.revision_type)
898 953
 
954
+    @raises(SameValueError)
955
+    def test_update_no_change(self):
956
+        uapi = UserApi(None)
957
+        groups = [
958
+            GroupApi(None).get_one(Group.TIM_USER),
959
+            GroupApi(None).get_one(Group.TIM_MANAGER),
960
+            GroupApi(None).get_one(Group.TIM_ADMIN)
961
+        ]
962
+
963
+        user1 = uapi.create_user(
964
+            email='this.is@user',
965
+            groups=groups,
966
+            save_now=True,
967
+        )
968
+
969
+        workspace = WorkspaceApi(user1).create_workspace(
970
+            'test workspace',
971
+            save_now=True
972
+        )
973
+
974
+        user2 = uapi.create_user()
975
+        user2.email = 'this.is@another.user'
976
+        uapi.save(user2)
977
+
978
+        RoleApi(user1).create_one(
979
+            user2,
980
+            workspace,
981
+            UserRoleInWorkspace.CONTENT_MANAGER,
982
+            with_notif=False,
983
+            flush=True
984
+        )
985
+        api = ContentApi(user1)
986
+        with DBSession.no_autoflush:
987
+            page = api.create(
988
+                content_type=ContentType.Page,
989
+                workspace=workspace,
990
+                label="same_content",
991
+                do_save=False
992
+            )
993
+            api.update_file_data(
994
+                page,
995
+                'index.html',
996
+                'text/html',
997
+                b'<html>Same Content Here</html>'
998
+            )
999
+        api.save(page, ActionDescription.CREATION, do_notify=True)
1000
+        transaction.commit()
1001
+
1002
+        api2 = ContentApi(user2)
1003
+        content2 = api2.get_one(page.content_id, ContentType.Any, workspace)
1004
+        with new_revision(content2):
1005
+            api2.update_file_data(
1006
+                page,
1007
+                'index.html',
1008
+                'text/html',
1009
+                b'<html>Same Content Here</html>'
1010
+            )
1011
+        api2.save(content2)
1012
+        transaction.commit()
1013
+
1014
+
899 1015
     def test_archive_unarchive(self):
900 1016
         uapi = UserApi(None)
901 1017
         groups = [GroupApi(None).get_one(Group.TIM_USER),