Browse Source

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

Bastien Sevajol 6 years ago
parent
commit
82d464775d
No account linked to committer's email

+ 58 - 44
tracim/tracim/controllers/content.py View File

174
 
174
 
175
     @property
175
     @property
176
     def _err_url(self):
176
     def _err_url(self):
177
-        return tg.url('/dashboard/workspaces/{}/folders/{}/file/{}')
177
+        return tg.url('/workspaces/{}/folders/{}/files/{}')
178
 
178
 
179
     @property
179
     @property
180
     def _item_type(self):
180
     def _item_type(self):
376
         try:
376
         try:
377
             api = ContentApi(tmpl_context.current_user)
377
             api = ContentApi(tmpl_context.current_user)
378
             item = api.get_one(int(item_id), self._item_type, workspace)
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
             with new_revision(item):
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
                     updated_item = api.update_content(
386
                     updated_item = api.update_content(
393
                         item, label if label else item.label,
387
                         item, label if label else item.label,
394
                         comment if comment else ''
388
                         comment if comment else ''
401
                         return render_invalid_integrity_chosen_path(
395
                         return render_invalid_integrity_chosen_path(
402
                             updated_item.get_label_as_file(),
396
                             updated_item.get_label_as_file(),
403
                         )
397
                         )
404
-
405
                     api.save(updated_item, ActionDescription.EDITION)
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
                 else:
399
                 else:
411
                     # So, now we may have a comment and/or a file revision
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
                     if isinstance(file_data, FieldStorage):
413
                     if isinstance(file_data, FieldStorage):
430
                         api.update_file_data(item,
414
                         api.update_file_data(item,
431
                                              file_data.filename,
415
                                              file_data.filename,
440
                             return render_invalid_integrity_chosen_path(
424
                             return render_invalid_integrity_chosen_path(
441
                                 item.get_label_as_file(),
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
                         api.save(item, ActionDescription.REVISION)
438
                         api.save(item, ActionDescription.REVISION)
439
+                    elif comment_item:
440
+                        api.save(comment_item, ActionDescription.COMMENT)
445
 
441
 
446
             msg = _('{} updated').format(self._item_type_label)
442
             msg = _('{} updated').format(self._item_type_label)
447
             tg.flash(msg, CST.STATUS_OK)
443
             tg.flash(msg, CST.STATUS_OK)
449
                                              tmpl_context.folder_id,
445
                                              tmpl_context.folder_id,
450
                                              item.content_id))
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
         except ValueError as e:
456
         except ValueError as e:
453
             error = '{} not updated - error: {}'
457
             error = '{} not updated - error: {}'
454
             msg = _(error).format(self._item_type_label,
458
             msg = _(error).format(self._item_type_label,
459
                                              item_id))
463
                                              item_id))
460
 
464
 
461
 
465
 
466
+
462
 class UserWorkspaceFolderPageRestController(TIMWorkspaceContentRestController):
467
 class UserWorkspaceFolderPageRestController(TIMWorkspaceContentRestController):
463
     """
468
     """
464
     manage a path like this: /workspaces/1/folders/XXX/pages/4
469
     manage a path like this: /workspaces/1/folders/XXX/pages/4
577
 
582
 
578
         tg.flash(_('Page created'), CST.STATUS_OK)
583
         tg.flash(_('Page created'), CST.STATUS_OK)
579
         redirect = '/workspaces/{}/folders/{}/pages/{}'
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
     @tg.require(current_user_is_contributor())
591
     @tg.require(current_user_is_contributor())
585
     @tg.expose()
592
     @tg.expose()
606
             tg.redirect(self._std_url.format(tmpl_context.workspace_id,
613
             tg.redirect(self._std_url.format(tmpl_context.workspace_id,
607
                                              tmpl_context.folder_id,
614
                                              tmpl_context.folder_id,
608
                                              item.content_id))
615
                                              item.content_id))
609
-        except SameValueError as e:
616
+        except SameValueError:
610
             not_updated = '{} not updated: the content did not change'
617
             not_updated = '{} not updated: the content did not change'
611
             msg = _(not_updated).format(self._item_type_label)
618
             msg = _(not_updated).format(self._item_type_label)
612
             tg.flash(msg, CST.STATUS_WARNING)
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
         except ValueError as e:
627
         except ValueError as e:
617
             not_updated = '{} not updated - error: {}'
628
             not_updated = '{} not updated - error: {}'
618
             msg = _(not_updated).format(self._item_type_label, str(e))
629
             msg = _(not_updated).format(self._item_type_label, str(e))
619
             tg.flash(msg, CST.STATUS_ERROR)
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
 class UserWorkspaceFolderThreadRestController(TIMWorkspaceContentRestController):
639
 class UserWorkspaceFolderThreadRestController(TIMWorkspaceContentRestController):
626
     """
640
     """

+ 6 - 1
tracim/tracim/lib/content.py View File

920
 
920
 
921
     def update_content(self, item: Content, new_label: str, new_content: str=None) -> Content:
921
     def update_content(self, item: Content, new_label: str, new_content: str=None) -> Content:
922
         if item.label==new_label and item.description==new_content:
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
         item.owner = self._user
926
         item.owner = self._user
925
         item.label = new_label
927
         item.label = new_label
926
         item.description = new_content if new_content else item.description # TODO: convert urls into links
928
         item.description = new_content if new_content else item.description # TODO: convert urls into links
928
         return item
930
         return item
929
 
931
 
930
     def update_file_data(self, item: Content, new_filename: str, new_mimetype: str, new_content: bytes) -> Content:
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
         item.owner = self._user
936
         item.owner = self._user
932
         item.file_name = new_filename
937
         item.file_name = new_filename
933
         item.file_mimetype = new_mimetype
938
         item.file_mimetype = new_mimetype

+ 31 - 7
tracim/tracim/lib/webdav/__init__.py View File

5
 from wsgidav import compat
5
 from wsgidav import compat
6
 
6
 
7
 from tracim.lib.content import ContentApi
7
 from tracim.lib.content import ContentApi
8
+from tracim.lib.utils import SameValueError
9
+from tracim.lib.base import logger
8
 from tracim.model import new_revision
10
 from tracim.model import new_revision
9
 from tracim.model.data import ActionDescription
11
 from tracim.model.data import ActionDescription
10
 from tracim.model.data import ContentType
12
 from tracim.model.data import ContentType
11
 from tracim.model.data import Content
13
 from tracim.model.data import Content
12
 from tracim.model.data import Workspace
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
 class HistoryType(object):
19
 class HistoryType(object):
96
 
100
 
97
         self._file_stream.seek(0)
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
     def create_file(self):
131
     def create_file(self):
107
         """
132
         """
138
                 util.guessMimeType(self._content.file_name),
163
                 util.guessMimeType(self._content.file_name),
139
                 self._file_stream.read()
164
                 self._file_stream.read()
140
             )
165
             )
141
-
142
             self._api.save(self._content, ActionDescription.REVISION)
166
             self._api.save(self._content, ActionDescription.REVISION)

+ 9 - 1
tracim/tracim/model/__init__.py View File

8
 from zope.sqlalchemy import ZopeTransactionExtension
8
 from zope.sqlalchemy import ZopeTransactionExtension
9
 
9
 
10
 from tracim.lib.exception import ContentRevisionUpdateError, ContentRevisionDeleteError
10
 from tracim.lib.exception import ContentRevisionUpdateError, ContentRevisionDeleteError
11
-
11
+from tracim.lib.utils import SameValueError
12
+import transaction
12
 
13
 
13
 class RevisionsIntegrity(object):
14
 class RevisionsIntegrity(object):
14
     """
15
     """
136
                 content.new_revision()
137
                 content.new_revision()
137
             RevisionsIntegrity.add_to_updatable(content.revision)
138
             RevisionsIntegrity.add_to_updatable(content.revision)
138
             yield content
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
         finally:
147
         finally:
140
             RevisionsIntegrity.remove_from_updatable(content.revision)
148
             RevisionsIntegrity.remove_from_updatable(content.revision)

+ 116 - 0
tracim/tracim/tests/library/test_content_api.py View File

11
 from tracim.lib.content import ContentApi
11
 from tracim.lib.content import ContentApi
12
 from tracim.lib.group import GroupApi
12
 from tracim.lib.group import GroupApi
13
 from tracim.lib.user import UserApi
13
 from tracim.lib.user import UserApi
14
+from tracim.lib.utils import SameValueError
14
 from tracim.lib.workspace import RoleApi
15
 from tracim.lib.workspace import RoleApi
15
 from tracim.lib.workspace import WorkspaceApi
16
 from tracim.lib.workspace import WorkspaceApi
16
 from tracim.model import DBSession, new_revision, User
17
 from tracim.model import DBSession, new_revision, User
829
         eq_('new content', updated.description)
830
         eq_('new content', updated.description)
830
         eq_(ActionDescription.EDITION, updated.revision_type)
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
     def test_update_file_data(self):
887
     def test_update_file_data(self):
833
         uapi = UserApi(None)
888
         uapi = UserApi(None)
834
         groups = [GroupApi(None).get_one(Group.TIM_USER),
889
         groups = [GroupApi(None).get_one(Group.TIM_USER),
896
         eq_(b'<html>hello world</html>', updated.depot_file.file.read())
951
         eq_(b'<html>hello world</html>', updated.depot_file.file.read())
897
         eq_(ActionDescription.REVISION, updated.revision_type)
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
     def test_archive_unarchive(self):
1015
     def test_archive_unarchive(self):
900
         uapi = UserApi(None)
1016
         uapi = UserApi(None)
901
         groups = [GroupApi(None).get_one(Group.TIM_USER),
1017
         groups = [GroupApi(None).get_one(Group.TIM_USER),