Browse Source

Merge branch 'lebouquetin-master'

Damien ACCORSI 10 years ago
parent
commit
960a4a6d7c

+ 3 - 30
tracim/tracim/controllers/__init__.py View File

126
 
126
 
127
 class TIMRestControllerWithBreadcrumb(TIMRestController):
127
 class TIMRestControllerWithBreadcrumb(TIMRestController):
128
 
128
 
129
-    def get_breadcrumb(self, folder_id=None) -> [BreadcrumbItem]:
129
+    def get_breadcrumb(self, item_id=None) -> [BreadcrumbItem]:
130
         """
130
         """
131
         TODO - Remove this and factorize it with other get_breadcrumb_xxx methods
131
         TODO - Remove this and factorize it with other get_breadcrumb_xxx methods
132
-        :param folder_id:
132
+        :param item_id: an item id (item may be normal content or folder
133
         :return:
133
         :return:
134
         """
134
         """
135
-        workspace = tmpl_context.workspace
136
-        workspace_id = tmpl_context.workspace_id
137
-        breadcrumb = []
138
-
139
-        breadcrumb.append(BreadcrumbItem(ContentType.icon(ContentType.FAKE_Dashboard), _('Workspaces'), tg.url('/workspaces')))
140
-        breadcrumb.append(BreadcrumbItem(ContentType.icon(ContentType.FAKE_Workspace), workspace.label, tg.url('/workspaces/{}'.format(workspace.workspace_id))))
141
-
142
-        content_api = ContentApi(tmpl_context.current_user)
143
-        if folder_id:
144
-            breadcrumb_folder_items = []
145
-            current_item = content_api.get_one(folder_id, ContentType.Any, workspace)
146
-            is_active = True
147
-
148
-            while current_item:
149
-                breadcrumb_item = BreadcrumbItem(ContentType.icon(current_item.type),
150
-                                                 current_item.label,
151
-                                                 tg.url('/workspaces/{}/folders/{}'.format(workspace_id, current_item.content_id)),
152
-                                                 is_active)
153
-                is_active = False # the first item is True, then all other are False => in the breadcrumb, only the last item is "active"
154
-                breadcrumb_folder_items.append(breadcrumb_item)
155
-                current_item = current_item.parent
156
-
157
-            for item in reversed(breadcrumb_folder_items):
158
-                breadcrumb.append(item)
159
-
160
-
161
-        return breadcrumb
162
-
135
+        return ContentApi(tmpl_context.current_user).build_breadcrumb(tmpl_context.workspace, item_id)
163
 
136
 
164
 
137
 
165
 class TIMWorkspaceContentRestController(TIMRestControllerWithBreadcrumb):
138
 class TIMWorkspaceContentRestController(TIMRestControllerWithBreadcrumb):

+ 0 - 1
tracim/tracim/controllers/content.py View File

762
     @tg.require(current_user_is_content_manager())
762
     @tg.require(current_user_is_content_manager())
763
     @tg.expose()
763
     @tg.expose()
764
     def put_archive_undo(self, item_id):
764
     def put_archive_undo(self, item_id):
765
-        print('AGAGA')
766
         # TODO - CHECK RIGHTS
765
         # TODO - CHECK RIGHTS
767
         item_id = int(item_id)
766
         item_id = int(item_id)
768
         content_api = ContentApi(tmpl_context.current_user, True, True) # Here we do not filter deleted items
767
         content_api = ContentApi(tmpl_context.current_user, True, True) # Here we do not filter deleted items

+ 25 - 0
tracim/tracim/controllers/root.py View File

123
         fake_api = Context(CTX.CURRENT_USER).toDict({'current_user': current_user_content})
123
         fake_api = Context(CTX.CURRENT_USER).toDict({'current_user': current_user_content})
124
 
124
 
125
         return DictLikeClass(fake_api=fake_api)
125
         return DictLikeClass(fake_api=fake_api)
126
+
127
+    @require(predicates.not_anonymous())
128
+    @expose('tracim.templates.search')
129
+    def search(self, keywords = ''):
130
+        from tracim.lib.content import ContentApi
131
+
132
+        user = tmpl_context.current_user
133
+        api = ContentApi(user)
134
+
135
+        items = []
136
+        keyword_list = api.get_keywords(keywords)
137
+
138
+        result = api.search(keyword_list)
139
+        if result:
140
+            items = result.limit(ContentApi.SEARCH_DEFAULT_RESULT_NB).all()
141
+
142
+        current_user_content = Context(CTX.CURRENT_USER).toDict(user)
143
+        fake_api = Context(CTX.CURRENT_USER).toDict({'current_user': current_user_content})
144
+
145
+        search_results = Context(CTX.SEARCH).toDict(items, 'results', 'result_nb')
146
+        search_results.keywords = keyword_list
147
+
148
+        return DictLikeClass(fake_api=fake_api, search=search_results)
149
+
150
+

+ 0 - 1
tracim/tracim/controllers/workspace.py View File

80
             dictified_workspaces = Context(CTX.MENU_API).toDict(workspaces, 'd')
80
             dictified_workspaces = Context(CTX.MENU_API).toDict(workspaces, 'd')
81
             return dictified_workspaces
81
             return dictified_workspaces
82
 
82
 
83
-
84
         allowed_content_types = ContentType.allowed_types_from_str(folder_allowed_content_types)
83
         allowed_content_types = ContentType.allowed_types_from_str(folder_allowed_content_types)
85
         ignored_item_ids = [int(ignore_id)] if ignore_id else []
84
         ignored_item_ids = [int(ignore_id)] if ignore_id else []
86
 
85
 

+ 152 - 6
tracim/tracim/i18n/fr/LC_MESSAGES/tracim.po View File

7
 msgstr ""
7
 msgstr ""
8
 "Project-Id-Version: pod 0.1\n"
8
 "Project-Id-Version: pod 0.1\n"
9
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
9
 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
10
+<<<<<<< HEAD
10
 "POT-Creation-Date: 2015-03-02 22:28+0100\n"
11
 "POT-Creation-Date: 2015-03-02 22:28+0100\n"
11
 "PO-Revision-Date: 2015-03-02 22:32+0100\n"
12
 "PO-Revision-Date: 2015-03-02 22:32+0100\n"
13
+=======
14
+"POT-Creation-Date: 2015-03-09 16:56+0100\n"
15
+"PO-Revision-Date: 2015-03-09 17:00+0100\n"
16
+>>>>>>> b2e4664ae2ad0cc8fbc3344f6fa37845cf7e344d
12
 "Last-Translator: Damien Accorsi <damien.accorsi@free.fr>\n"
17
 "Last-Translator: Damien Accorsi <damien.accorsi@free.fr>\n"
13
 "Language-Team: fr_FR <LL@li.org>\n"
18
 "Language-Team: fr_FR <LL@li.org>\n"
14
 "Plural-Forms: nplurals=2; plural=(n > 1)\n"
19
 "Plural-Forms: nplurals=2; plural=(n > 1)\n"
81
 msgid "Password changed successfully"
86
 msgid "Password changed successfully"
82
 msgstr "Mot de passe changé"
87
 msgstr "Mot de passe changé"
83
 
88
 
89
+<<<<<<< HEAD
84
 #: tracim/controllers/__init__.py:139
90
 #: tracim/controllers/__init__.py:139
85
 #: tracim/templates/dashboard.mak:36
91
 #: tracim/templates/dashboard.mak:36
86
 #: tracim/templates/master_authenticated.mak:94
92
 #: tracim/templates/master_authenticated.mak:94
98
 msgstr "Espaces de travail"
104
 msgstr "Espaces de travail"
99
 
105
 
100
 #: tracim/controllers/__init__.py:274
106
 #: tracim/controllers/__init__.py:274
107
+=======
108
+#: tracim/controllers/__init__.py:247
109
+>>>>>>> b2e4664ae2ad0cc8fbc3344f6fa37845cf7e344d
101
 #: tracim/controllers/content.py:283
110
 #: tracim/controllers/content.py:283
102
 msgid "{} updated"
111
 msgid "{} updated"
103
 msgstr "{} mis(e) à jour"
112
 msgstr "{} mis(e) à jour"
104
 
113
 
114
+<<<<<<< HEAD
105
 #: tracim/controllers/__init__.py:279
115
 #: tracim/controllers/__init__.py:279
116
+=======
117
+#: tracim/controllers/__init__.py:252
118
+>>>>>>> b2e4664ae2ad0cc8fbc3344f6fa37845cf7e344d
106
 #: tracim/controllers/content.py:288
119
 #: tracim/controllers/content.py:288
107
 msgid "{} not updated - error: {}"
120
 msgid "{} not updated - error: {}"
108
 msgstr "{} pas mis(e) à jour - erreur : {}"
121
 msgstr "{} pas mis(e) à jour - erreur : {}"
109
 
122
 
123
+<<<<<<< HEAD
110
 #: tracim/controllers/__init__.py:293
124
 #: tracim/controllers/__init__.py:293
111
 msgid "{} status updated"
125
 msgid "{} status updated"
112
 msgstr "Statut de {} mis(e) à jour"
126
 msgstr "Statut de {} mis(e) à jour"
116
 msgstr "Statut de {} non mis(e) à jour : {}"
130
 msgstr "Statut de {} non mis(e) à jour : {}"
117
 
131
 
118
 #: tracim/controllers/__init__.py:329
132
 #: tracim/controllers/__init__.py:329
133
+=======
134
+#: tracim/controllers/__init__.py:266
135
+msgid "{} status updated"
136
+msgstr "Statut de {} mis(e) à jour"
137
+
138
+#: tracim/controllers/__init__.py:270
139
+msgid "{} status not updated: {}"
140
+msgstr "Statut de {} non mis(e) à jour : {}"
141
+
142
+#: tracim/controllers/__init__.py:302
143
+>>>>>>> b2e4664ae2ad0cc8fbc3344f6fa37845cf7e344d
119
 #: tracim/controllers/content.py:748
144
 #: tracim/controllers/content.py:748
120
 msgid "{} archived. <a class=\"alert-link\" href=\"{}\">Cancel action</a>"
145
 msgid "{} archived. <a class=\"alert-link\" href=\"{}\">Cancel action</a>"
121
 msgstr "{} archivé(e). <a class=\"alert-link\" href=\"{}\">Annuler l'opération</a>"
146
 msgstr "{} archivé(e). <a class=\"alert-link\" href=\"{}\">Annuler l'opération</a>"
122
 
147
 
148
+<<<<<<< HEAD
123
 #: tracim/controllers/__init__.py:338
149
 #: tracim/controllers/__init__.py:338
150
+=======
151
+#: tracim/controllers/__init__.py:311
152
+>>>>>>> b2e4664ae2ad0cc8fbc3344f6fa37845cf7e344d
124
 #: tracim/controllers/content.py:757
153
 #: tracim/controllers/content.py:757
125
 msgid "{} not archived: {}"
154
 msgid "{} not archived: {}"
126
 msgstr "{} non archivé(e) : {}"
155
 msgstr "{} non archivé(e) : {}"
127
 
156
 
157
+<<<<<<< HEAD
128
 #: tracim/controllers/__init__.py:352
158
 #: tracim/controllers/__init__.py:352
129
 #: tracim/controllers/content.py:772
159
 #: tracim/controllers/content.py:772
130
 msgid "{} unarchived."
160
 msgid "{} unarchived."
156
 #: tracim/controllers/__init__.py:412
186
 #: tracim/controllers/__init__.py:412
157
 #: tracim/controllers/content.py:128
187
 #: tracim/controllers/content.py:128
158
 #: tracim/controllers/content.py:831
188
 #: tracim/controllers/content.py:831
189
+=======
190
+#: tracim/controllers/__init__.py:325
191
+#: tracim/controllers/content.py:771
192
+msgid "{} unarchived."
193
+msgstr "{} désarchivé(e)"
194
+
195
+#: tracim/controllers/__init__.py:333
196
+#: tracim/controllers/content.py:779
197
+msgid "{} not un-archived: {}"
198
+msgstr "{} non désarchivé(e) : {}"
199
+
200
+#: tracim/controllers/__init__.py:351
201
+#: tracim/controllers/content.py:88
202
+#: tracim/controllers/content.py:796
203
+msgid "{} deleted. <a class=\"alert-link\" href=\"{}\">Cancel action</a>"
204
+msgstr "{} supprimé(e). <a class=\"alert-link\" href=\"{}\">Annuler l'opération</a>"
205
+
206
+#: tracim/controllers/__init__.py:360
207
+#: tracim/controllers/content.py:99
208
+#: tracim/controllers/content.py:805
209
+msgid "{} not deleted: {}"
210
+msgstr "{} non supprimé(e) : {}"
211
+
212
+#: tracim/controllers/__init__.py:375
213
+#: tracim/controllers/content.py:116
214
+#: tracim/controllers/content.py:820
215
+msgid "{} undeleted."
216
+msgstr "{} restauré(e)."
217
+
218
+#: tracim/controllers/__init__.py:385
219
+#: tracim/controllers/content.py:128
220
+#: tracim/controllers/content.py:830
221
+>>>>>>> b2e4664ae2ad0cc8fbc3344f6fa37845cf7e344d
159
 msgid "{} not un-deleted: {}"
222
 msgid "{} not un-deleted: {}"
160
 msgstr "{} non restauré(e) : {}"
223
 msgstr "{} non restauré(e) : {}"
161
 
224
 
220
 #: tracim/controllers/content.py:730
283
 #: tracim/controllers/content.py:730
221
 #| msgid "Folders"
284
 #| msgid "Folders"
222
 msgid "Folder"
285
 msgid "Folder"
286
+<<<<<<< HEAD
223
 msgstr "Dossier"
287
 msgstr "Dossier"
288
+=======
289
+msgstr "Dossiers"
290
+>>>>>>> b2e4664ae2ad0cc8fbc3344f6fa37845cf7e344d
224
 
291
 
225
 #: tracim/controllers/root.py:72
292
 #: tracim/controllers/root.py:72
226
 msgid "Wrong credentials"
293
 msgid "Wrong credentials"
365
 msgid "%B %d at %I:%M%p"
432
 msgid "%B %d at %I:%M%p"
366
 msgstr "le %d %B à %H:%M"
433
 msgstr "le %d %B à %H:%M"
367
 
434
 
435
+<<<<<<< HEAD
436
+=======
437
+#: tracim/lib/content.py:98
438
+#: tracim/templates/dashboard.mak:36
439
+#: tracim/templates/master_authenticated.mak:94
440
+#: tracim/templates/master_no_toolbar_no_login.mak:112
441
+#: tracim/templates/search.mak:11
442
+#: tracim/templates/user_get_one.mak:39
443
+#: tracim/templates/user_profile.mak:39
444
+#: tracim/templates/user_workspace_folder_file_get_one.mak:10
445
+#: tracim/templates/user_workspace_folder_get_one.mak:11
446
+#: tracim/templates/user_workspace_folder_page_get_one.mak:11
447
+#: tracim/templates/user_workspace_folder_thread_get_one.mak:11
448
+#: tracim/templates/user_workspace_get_all.mak:11
449
+#: tracim/templates/user_workspace_get_one.mak:11
450
+#: tracim/templates/workspace_get_all.mak:13
451
+msgid "Workspaces"
452
+msgstr "Espaces de travail"
453
+
454
+>>>>>>> b2e4664ae2ad0cc8fbc3344f6fa37845cf7e344d
368
 #: tracim/lib/predicates.py:17
455
 #: tracim/lib/predicates.py:17
369
 msgid "You are not authorized to access this resource"
456
 msgid "You are not authorized to access this resource"
370
 msgstr "Vous n'êtes pas autorisé à accéder à cette ressource"
457
 msgstr "Vous n'êtes pas autorisé à accéder à cette ressource"
490
 #: tracim/model/data.py:292
577
 #: tracim/model/data.py:292
491
 #| msgid "Delete current workspace"
578
 #| msgid "Delete current workspace"
492
 msgid "Delete this workspace"
579
 msgid "Delete this workspace"
580
+<<<<<<< HEAD
493
 msgstr "Supprimer cet espace de travail"
581
 msgstr "Supprimer cet espace de travail"
582
+=======
583
+msgstr "Supprimer l'espace de travail"
584
+>>>>>>> b2e4664ae2ad0cc8fbc3344f6fa37845cf7e344d
494
 
585
 
495
 #: tracim/model/data.py:293
586
 #: tracim/model/data.py:293
496
 #| msgid "Create this folder"
587
 #| msgid "Create this folder"
500
 #: tracim/model/data.py:294
591
 #: tracim/model/data.py:294
501
 #| msgid "Delete file"
592
 #| msgid "Delete file"
502
 msgid "Delete this file"
593
 msgid "Delete this file"
594
+<<<<<<< HEAD
503
 msgstr "Supprimer ce fichier"
595
 msgstr "Supprimer ce fichier"
596
+=======
597
+msgstr "Supprimer le fichier"
598
+>>>>>>> b2e4664ae2ad0cc8fbc3344f6fa37845cf7e344d
504
 
599
 
505
 #: tracim/model/data.py:295
600
 #: tracim/model/data.py:295
506
 #| msgid "Create this page"
601
 #| msgid "Create this page"
618
 
713
 
619
 #: tracim/templates/index.mak:30
714
 #: tracim/templates/index.mak:30
620
 #: tracim/templates/index.mak:51
715
 #: tracim/templates/index.mak:51
716
+<<<<<<< HEAD
621
 #: tracim/templates/master_authenticated.mak:147
717
 #: tracim/templates/master_authenticated.mak:147
718
+=======
719
+#: tracim/templates/master_authenticated.mak:154
720
+>>>>>>> b2e4664ae2ad0cc8fbc3344f6fa37845cf7e344d
622
 msgid "Login"
721
 msgid "Login"
623
 msgstr "Login"
722
 msgstr "Login"
624
 
723
 
662
 msgid "you MUST desactivate debug in production"
761
 msgid "you MUST desactivate debug in production"
663
 msgstr "vous DEVEZ désactiver le mode \"debug\" en production"
762
 msgstr "vous DEVEZ désactiver le mode \"debug\" en production"
664
 
763
 
764
+<<<<<<< HEAD
665
 #: tracim/templates/master_authenticated.mak:137
765
 #: tracim/templates/master_authenticated.mak:137
766
+=======
767
+#: tracim/templates/master_authenticated.mak:119
768
+#| msgid "Search..."
769
+msgid "Search for..."
770
+msgstr "Rechercher..."
771
+
772
+#: tracim/templates/master_authenticated.mak:121
773
+#| msgid "Search..."
774
+msgid "Search"
775
+msgstr "Rechercher..."
776
+
777
+#: tracim/templates/master_authenticated.mak:144
778
+>>>>>>> b2e4664ae2ad0cc8fbc3344f6fa37845cf7e344d
666
 #: tracim/templates/master_no_toolbar_no_login.mak:144
779
 #: tracim/templates/master_no_toolbar_no_login.mak:144
667
 msgid "My account"
780
 msgid "My account"
668
 msgstr "Mon compte"
781
 msgstr "Mon compte"
669
 
782
 
783
+<<<<<<< HEAD
670
 #: tracim/templates/master_authenticated.mak:142
784
 #: tracim/templates/master_authenticated.mak:142
785
+=======
786
+#: tracim/templates/master_authenticated.mak:149
787
+>>>>>>> b2e4664ae2ad0cc8fbc3344f6fa37845cf7e344d
671
 #: tracim/templates/master_no_toolbar_no_login.mak:149
788
 #: tracim/templates/master_no_toolbar_no_login.mak:149
672
 msgid "Logout"
789
 msgid "Logout"
673
 msgstr "Fermer la session"
790
 msgstr "Fermer la session"
735
 msgid "Password Reset Request"
852
 msgid "Password Reset Request"
736
 msgstr "Requête de réinitialisation du mot de passe"
853
 msgstr "Requête de réinitialisation du mot de passe"
737
 
854
 
855
+#: tracim/templates/search.mak:8
856
+#: tracim/templates/user_get_me.mak:39
857
+#: tracim/templates/user_workspace_get_all.mak:8
858
+#: tracim/templates/user_workspace_get_all.mak:29
859
+msgid "My workspaces"
860
+msgstr "Mes espaces de travail"
861
+
862
+#: tracim/templates/search.mak:31
863
+msgid "Search results"
864
+msgstr "Résultats de recherche"
865
+
866
+#: tracim/templates/search.mak:33
867
+msgid "for keywords: "
868
+msgstr "pour les mot-clés :"
869
+
870
+#: tracim/templates/search.mak:41
871
+msgid "loading..."
872
+msgstr "chargement..."
873
+
874
+#: tracim/templates/search.mak:67
875
+msgid "Last known activty"
876
+msgstr "Dernière activité connue"
877
+
878
+#: tracim/templates/search_toolbars.mak:62
879
+msgid "Showing {0} filtered items of {1}"
880
+msgstr "Affichage de {0} éléments sur un total de {1}"
881
+
882
+#: tracim/templates/search_toolbars.mak:64
883
+msgid "Showing {0} filtered item of a total of {1}"
884
+msgstr "Affichage de {0} élément sur un total de {1}"
885
+
886
+#: tracim/templates/search_toolbars.mak:66
887
+msgid "Showing all items. You can filter by clicking right toolbar buttons."
888
+msgstr "Affichage de tous les résultats. Vous pouvez les filtrer à l'aide de la barre d'outils à droite"
889
+
738
 #: tracim/templates/thread_toolbars.mak:8
890
 #: tracim/templates/thread_toolbars.mak:8
739
 msgid "Edit current thread"
891
 msgid "Edit current thread"
740
 msgstr "Modifier le sujet de discussion"
892
 msgstr "Modifier le sujet de discussion"
841
 msgid "I am an administrator."
993
 msgid "I am an administrator."
842
 msgstr "Je suis un administrateur."
994
 msgstr "Je suis un administrateur."
843
 
995
 
844
-#: tracim/templates/user_get_me.mak:39
845
-#: tracim/templates/user_workspace_get_all.mak:8
846
-#: tracim/templates/user_workspace_get_all.mak:29
847
-msgid "My workspaces"
848
-msgstr "Mes espaces de travail"
849
-
850
 #: tracim/templates/user_get_me.mak:42
996
 #: tracim/templates/user_get_me.mak:42
851
 msgid "I'm not member of any workspace."
997
 msgid "I'm not member of any workspace."
852
 msgstr "Je ne suis membre d'aucun espace de travail"
998
 msgstr "Je ne suis membre d'aucun espace de travail"

+ 1 - 1
tracim/tracim/lib/base.py View File

113
         self._logger = logging.getLogger(self._name)
113
         self._logger = logging.getLogger(self._name)
114
 
114
 
115
     def _txt(self, instance_or_class):
115
     def _txt(self, instance_or_class):
116
-        if instance_or_class.__class__.__name__=='type':
116
+        if instance_or_class.__class__.__name__ in ('function', 'type'):
117
             return instance_or_class.__name__
117
             return instance_or_class.__name__
118
         else:
118
         else:
119
             return instance_or_class.__class__.__name__
119
             return instance_or_class.__class__.__name__

+ 122 - 4
tracim/tracim/lib/content.py View File

2
 
2
 
3
 __author__ = 'damien'
3
 __author__ = 'damien'
4
 
4
 
5
+import re
6
+
5
 import tg
7
 import tg
8
+from tg.i18n import ugettext as _
6
 
9
 
10
+import sqlalchemy
11
+from sqlalchemy.orm import aliased
12
+from sqlalchemy.orm import joinedload
7
 from sqlalchemy.orm.attributes import get_history
13
 from sqlalchemy.orm.attributes import get_history
8
 from sqlalchemy import not_
14
 from sqlalchemy import not_
15
+from sqlalchemy import or_
9
 from tracim.lib import cmp_to_key
16
 from tracim.lib import cmp_to_key
10
 from tracim.lib.notifications import NotifierFactory
17
 from tracim.lib.notifications import NotifierFactory
11
 from tracim.model import DBSession
18
 from tracim.model import DBSession
12
 from tracim.model.auth import User
19
 from tracim.model.auth import User
13
-from tracim.model.data import ContentStatus, ContentRevisionRO, ActionDescription
20
+from tracim.model.data import ActionDescription
21
+from tracim.model.data import BreadcrumbItem
22
+from tracim.model.data import ContentStatus
23
+from tracim.model.data import ContentRevisionRO
14
 from tracim.model.data import Content
24
 from tracim.model.data import Content
15
 from tracim.model.data import ContentType
25
 from tracim.model.data import ContentType
16
 from tracim.model.data import NodeTreeItem
26
 from tracim.model.data import NodeTreeItem
48
 
58
 
49
 class ContentApi(object):
59
 class ContentApi(object):
50
 
60
 
61
+    SEARCH_SEPARATORS = ',| '
62
+    SEARCH_DEFAULT_RESULT_NB = 50
63
+
64
+
51
     def __init__(self, current_user: User, show_archived=False, show_deleted=False, all_content_in_treeview=True):
65
     def __init__(self, current_user: User, show_archived=False, show_deleted=False, all_content_in_treeview=True):
52
         self._user = current_user
66
         self._user = current_user
53
         self._show_archived = show_archived
67
         self._show_archived = show_archived
71
         content_list.sort(key=cmp_to_key(compare_content_for_sorting_by_type_and_name))
85
         content_list.sort(key=cmp_to_key(compare_content_for_sorting_by_type_and_name))
72
         return content_list
86
         return content_list
73
 
87
 
74
-
75
-    def _base_query(self, workspace: Workspace=None):
88
+    def build_breadcrumb(self, workspace, item_id=None, skip_root=False) -> [BreadcrumbItem]:
89
+        """
90
+        TODO - Remove this and factorize it with other get_breadcrumb_xxx methods
91
+        :param item_id: an item id (item may be normal content or folder
92
+        :return:
93
+        """
94
+        workspace_id = workspace.workspace_id
95
+        breadcrumb = []
96
+
97
+        if not skip_root:
98
+            breadcrumb.append(BreadcrumbItem(ContentType.icon(ContentType.FAKE_Dashboard), _('Workspaces'), tg.url('/workspaces')))
99
+        breadcrumb.append(BreadcrumbItem(ContentType.icon(ContentType.FAKE_Workspace), workspace.label, tg.url('/workspaces/{}'.format(workspace.workspace_id))))
100
+
101
+        if item_id:
102
+            breadcrumb_folder_items = []
103
+            current_item = self.get_one(item_id, ContentType.Any, workspace)
104
+            is_active = True
105
+            if current_item.type==ContentType.Folder:
106
+                next_url = tg.url('/workspaces/{}/folders/{}'.format(workspace_id, current_item.content_id))
107
+            else:
108
+                next_url = tg.url('/workspaces/{}/folders/{}/{}s/{}'.format(workspace_id, current_item.parent_id, current_item.type, current_item.content_id))
109
+
110
+            while current_item:
111
+                breadcrumb_item = BreadcrumbItem(ContentType.icon(current_item.type),
112
+                                                 current_item.label,
113
+                                                 next_url,
114
+                                                 is_active)
115
+                is_active = False # the first item is True, then all other are False => in the breadcrumb, only the last item is "active"
116
+                breadcrumb_folder_items.append(breadcrumb_item)
117
+                current_item = current_item.parent
118
+                if current_item:
119
+                    # In last iteration, the parent is None, and there is no more breadcrumb item to build
120
+                    next_url = tg.url('/workspaces/{}/folders/{}'.format(workspace_id, current_item.content_id))
121
+
122
+            for item in reversed(breadcrumb_folder_items):
123
+                breadcrumb.append(item)
124
+
125
+
126
+        return breadcrumb
127
+
128
+    def __real_base_query(self, workspace: Workspace=None):
76
         result = DBSession.query(Content)
129
         result = DBSession.query(Content)
77
 
130
 
78
         if workspace:
131
         if workspace:
79
             result = result.filter(Content.workspace_id==workspace.workspace_id)
132
             result = result.filter(Content.workspace_id==workspace.workspace_id)
80
 
133
 
134
+        return result
135
+
136
+    def _base_query(self, workspace: Workspace=None):
137
+        result = self.__real_base_query(workspace)
138
+
81
         if not self._show_deleted:
139
         if not self._show_deleted:
82
             result = result.filter(Content.is_deleted==False)
140
             result = result.filter(Content.is_deleted==False)
83
 
141
 
86
 
144
 
87
         return result
145
         return result
88
 
146
 
147
+    def _hard_filtered_base_query(self, workspace: Workspace=None):
148
+        """
149
+        If set to True, then filterign on is_deleted and is_archived will also
150
+        filter parent properties. This is required for search() function which
151
+        also search in comments (for example) which may be 'not deleted' while
152
+        the associated content is deleted
153
+
154
+        :param hard_filtering:
155
+        :return:
156
+        """
157
+        result = self.__real_base_query(workspace)
158
+
159
+        if not self._show_deleted:
160
+            parent = aliased(Content)
161
+            result = result.join(parent, Content.parent).\
162
+                filter(Content.is_deleted==False).\
163
+                filter(parent.is_deleted==False)
164
+
165
+        if not self._show_archived:
166
+            parent = aliased(Content)
167
+            result = result.join(parent, Content.parent).\
168
+                filter(Content.is_archived==False).\
169
+                filter(parent.is_archived==False)
170
+
171
+        return result
172
+
89
     def get_child_folders(self, parent: Content=None, workspace: Workspace=None, filter_by_allowed_content_types: list=[], removed_item_ids: list=[], allowed_node_types=None) -> [Content]:
173
     def get_child_folders(self, parent: Content=None, workspace: Workspace=None, filter_by_allowed_content_types: list=[], removed_item_ids: list=[], allowed_node_types=None) -> [Content]:
90
         """
174
         """
91
         This method returns child items (folders or items) for left bar treeview.
175
         This method returns child items (folders or items) for left bar treeview.
110
             all()
194
             all()
111
 
195
 
112
         if not filter_by_allowed_content_types or len(filter_by_allowed_content_types)<=0:
196
         if not filter_by_allowed_content_types or len(filter_by_allowed_content_types)<=0:
113
-            return folders
197
+            filter_by_allowed_content_types = ContentType.allowed_types_for_folding()
114
 
198
 
115
         # Now, the case is to filter folders by the content that they are allowed to contain
199
         # Now, the case is to filter folders by the content that they are allowed to contain
116
         result = []
200
         result = []
118
             for allowed_content_type in filter_by_allowed_content_types:
202
             for allowed_content_type in filter_by_allowed_content_types:
119
                 if folder.type==ContentType.Folder and folder.properties['allowed_content'][allowed_content_type]==True:
203
                 if folder.type==ContentType.Folder and folder.properties['allowed_content'][allowed_content_type]==True:
120
                     result.append(folder)
204
                     result.append(folder)
205
+                    break
121
 
206
 
122
         return result
207
         return result
123
 
208
 
290
 
375
 
291
 
376
 
292
         if do_flush:
377
         if do_flush:
378
+            DBSession.add(content)
293
             DBSession.flush()
379
             DBSession.flush()
294
 
380
 
295
         if do_notify:
381
         if do_notify:
296
             NotifierFactory.create(self._user).notify_content_update(content)
382
             NotifierFactory.create(self._user).notify_content_update(content)
297
 
383
 
384
+
385
+    def get_keywords(self, search_string, search_string_separators=None) -> [str]:
386
+        """
387
+        :param search_string: a list of coma-separated keywords
388
+        :return: a list of str (each keyword = 1 entry
389
+        """
390
+
391
+        search_string_separators = search_string_separators or ContentApi.SEARCH_SEPARATORS
392
+
393
+        keywords = []
394
+        if search_string:
395
+            keywords = [keyword.strip() for keyword in re.split(search_string_separators, search_string)]
396
+
397
+        return keywords
398
+
399
+    def search(self, keywords: [str]) -> sqlalchemy.orm.query.Query:
400
+        """
401
+        :return: a sorted list of Content items
402
+        """
403
+
404
+        if len(keywords)<=0:
405
+            return None
406
+
407
+        filter_group_label = list(Content.label.ilike('%{}%'.format(keyword)) for keyword in keywords)
408
+        filter_group_desc = list(Content.description.ilike('%{}%'.format(keyword)) for keyword in keywords)
409
+        title_keyworded_items = self._hard_filtered_base_query().\
410
+            filter(or_(*(filter_group_label+filter_group_desc))).\
411
+            options(joinedload('children')).\
412
+            options(joinedload('parent'))
413
+
414
+        return title_keyworded_items
415
+

+ 11 - 0
tracim/tracim/lib/helpers.py View File

180
             return True
180
             return True
181
     return False
181
     return False
182
 
182
 
183
+def shorten(text: str, maxlength: int, add_three_points=True) -> str:
184
+
185
+    result = text
186
+    if len(text)>maxlength:
187
+        result = text[:maxlength]
188
+
189
+        if add_three_points:
190
+            result += '…'
191
+
192
+    return result
193
+
183
 from tracim.config.app_cfg import CFG as CFG_ORI
194
 from tracim.config.app_cfg import CFG as CFG_ORI
184
 CFG = CFG_ORI.get_instance() # local CFG var is an instance of CFG class found in app_cfg
195
 CFG = CFG_ORI.get_instance() # local CFG var is an instance of CFG class found in app_cfg

+ 17 - 0
tracim/tracim/lib/utils.py View File

1
+# -*- coding: utf-8 -*-
2
+
3
+import time
4
+
5
+from tracim.lib.base import logger
6
+
7
+def exec_time_monitor():
8
+    def decorator_func(func):
9
+        def wrapper_func(*args, **kwargs):
10
+            start = time.time()
11
+            retval = func(*args, **kwargs)
12
+            end = time.time()
13
+            logger.debug(func, 'exec time: {} seconds'.format(end-start))
14
+            return retval
15
+        return wrapper_func
16
+    return decorator_func
17
+

+ 6 - 1
tracim/tracim/model/data.py View File

311
         return [cls.Folder, cls.File, cls.Comment, cls.Thread, cls.Page]
311
         return [cls.Folder, cls.File, cls.Comment, cls.Thread, cls.Page]
312
 
312
 
313
     @classmethod
313
     @classmethod
314
+    def allowed_types_for_folding(cls):
315
+        # This method is used for showing only "main" types in the left-side treeview
316
+        return [cls.Folder, cls.File, cls.Thread, cls.Page]
317
+
318
+    @classmethod
314
     def allowed_types_from_str(cls, allowed_types_as_string: str):
319
     def allowed_types_from_str(cls, allowed_types_as_string: str):
315
         allowed_types = []
320
         allowed_types = []
316
         # HACK - THIS
321
         # HACK - THIS
317
         for item in allowed_types_as_string.split(ContentType._STRING_LIST_SEPARATOR):
322
         for item in allowed_types_as_string.split(ContentType._STRING_LIST_SEPARATOR):
318
-            if item and item in ContentType.allowed_types():
323
+            if item and item in ContentType.allowed_types_for_folding():
319
                 allowed_types.append(item)
324
                 allowed_types.append(item)
320
         return allowed_types
325
         return allowed_types
321
 
326
 

+ 63 - 12
tracim/tracim/model/serializers.py View File

1
 # -*- coding: utf-8 -*-
1
 # -*- coding: utf-8 -*-
2
 import types
2
 import types
3
 
3
 
4
+from bs4 import BeautifulSoup
4
 import tg
5
 import tg
5
 from tg.util import LazyString
6
 from tg.util import LazyString
6
 from tracim.lib.base import logger
7
 from tracim.lib.base import logger
7
-from tracim.model.auth import Group, Profile
8
+from tracim.lib.utils import exec_time_monitor
9
+from tracim.model.auth import Profile
8
 from tracim.model.auth import User
10
 from tracim.model.auth import User
9
 from tracim.model.data import BreadcrumbItem, ActionDescription
11
 from tracim.model.data import BreadcrumbItem, ActionDescription
10
 from tracim.model.data import ContentStatus
12
 from tracim.model.data import ContentStatus
47
         Context.register_converter(self.context, self.model_class, func)
49
         Context.register_converter(self.context, self.model_class, func)
48
         return func
50
         return func
49
 
51
 
50
-
51
 class ContextConverterNotFoundException(Exception):
52
 class ContextConverterNotFoundException(Exception):
52
     def __init__(self, context_string, model_class):
53
     def __init__(self, context_string, model_class):
53
         message = 'converter not found (context: {0} - model: {1})'.format(context_string, model_class.__name__)
54
         message = 'converter not found (context: {0} - model: {1})'.format(context_string, model_class.__name__)
68
     MENU_API_BUILD_FROM_TREE_ITEM = 'MENU_API_BUILD_FROM_TREE_ITEM'
69
     MENU_API_BUILD_FROM_TREE_ITEM = 'MENU_API_BUILD_FROM_TREE_ITEM'
69
     PAGE = 'PAGE'
70
     PAGE = 'PAGE'
70
     PAGES = 'PAGES'
71
     PAGES = 'PAGES'
72
+    SEARCH = 'SEARCH'
71
     THREAD = 'THREAD'
73
     THREAD = 'THREAD'
72
     THREADS = 'THREADS'
74
     THREADS = 'THREADS'
73
     USER = 'USER'
75
     USER = 'USER'
412
 
414
 
413
     if item.type==ContentType.Folder:
415
     if item.type==ContentType.Folder:
414
         return Context(CTX.DEFAULT).toDict(item)
416
         return Context(CTX.DEFAULT).toDict(item)
415
-    ### CODE BELOW IS REPLACED BY THE TWO LINES UP ^^
416
-    # 2014-10-08 - IF YOU FIND THIS COMMENT, YOU CAn REMOVE THE CODE
417
-    #
418
-    #if item.type==ContentType.Folder:
419
-    #    value = DictLikeClass(
420
-    #        id = item.content_id,
421
-    #        label = item.label,
422
-    #    )
423
-    #    return value
424
 
417
 
425
-    raise NotImplementedError
426
 
418
 
427
 
419
 
428
 @pod_serializer(Content, CTX.THREADS)
420
 @pod_serializer(Content, CTX.THREADS)
556
     return result
548
     return result
557
 
549
 
558
 
550
 
551
+from tg import cache
552
+
553
+@pod_serializer(Content, CTX.SEARCH)
554
+def serialize_content_for_search_result(content: Content, context: Context):
555
+
556
+    def serialize_it():
557
+        nonlocal content
558
+
559
+        if content.type == ContentType.Comment:
560
+            logger.info('serialize_content_for_search_result', 'Serializing parent class {} instead of {} [content #{}]'.format(content.parent.type, content.type, content.content_id))
561
+            content = content.parent
562
+
563
+        data_container = content
564
+
565
+        if content.revision_to_serialize>0:
566
+            for revision in content.revisions:
567
+                if revision.revision_id==content.revision_to_serialize:
568
+                    data_container = revision
569
+                    break
570
+
571
+        # FIXME - D.A. - 2015-02-23 - This import should not be there...
572
+        from tracim.lib.content import ContentApi
573
+        breadcrumbs = ContentApi(None).build_breadcrumb(data_container.workspace, data_container.content_id, skip_root=True)
574
+
575
+        last_comment_datetime = data_container.updated
576
+        comments = data_container.get_comments()
577
+        if comments:
578
+            last_comment_datetime = max(last_comment_datetime, max(comment.updated for comment in comments))
579
+
580
+        result = DictLikeClass(
581
+            id = content.content_id,
582
+            parent = context.toDict(content.parent),
583
+            workspace = context.toDict(content.workspace),
584
+            type = content.type,
585
+
586
+            content = data_container.description,
587
+            content_raw = BeautifulSoup(data_container.description).text,
588
+
589
+            created = data_container.created,
590
+            label = data_container.label,
591
+            icon = ContentType.icon(content.type),
592
+            owner = context.toDict(data_container.owner),
593
+            status = context.toDict(data_container.get_status()),
594
+            breadcrumb = context.toDict(breadcrumbs),
595
+            last_activity = last_comment_datetime
596
+        )
597
+
598
+        if content.type==ContentType.File:
599
+            result.label = content.label.__str__() if content.label else content.file_name.__str__()
600
+
601
+        if not result.label or ''==result.label:
602
+            result.label = 'No title'
603
+
604
+        return result
605
+
606
+    return serialize_it()
607
+
608
+
609
+
559
 ########################################################################################################################
610
 ########################################################################################################################
560
 # ContentStatus
611
 # ContentStatus
561
 
612
 

+ 17 - 3
tracim/tracim/public/assets/css/dashboard.css View File

178
 
178
 
179
 table.first_row_headers tr:first-child td {
179
 table.first_row_headers tr:first-child td {
180
     font-weight: bold;
180
     font-weight: bold;
181
-    background-color: #EEE;
181
+    background-color: #EEE;
182
     align: center;
182
     align: center;
183
 }
183
 }
184
 
184
 
185
 table.first_column_headers tr td:first-child {
185
 table.first_column_headers tr td:first-child {
186
     font-weight: bold;
186
     font-weight: bold;
187
     background-color: #EEE;
187
     background-color: #EEE;
188
-    align: center;
189
-}
188
+    align: center;
189
+}
190
+
191
+.search-result-item-breadcrumb {
192
+    margin-top: -1em;
193
+}
194
+
195
+.search-result-item-breadcrumb a {
196
+    color: #090;
197
+}
198
+
199
+#search-result-dynamic-resume {
200
+    color: #090;
201
+    margin-top: -1em;
202
+    margin-bottom: 0;
203
+}

BIN
tracim/tracim/public/assets/icons/32x32/emblems/emblem-checked.png View File


BIN
tracim/tracim/public/assets/icons/32x32/places/jstree-folder.png View File


BIN
tracim/tracim/public/assets/icons/32x32/status/status-open.png View File


BIN
tracim/tracim/public/assets/icons/32x32/status/status-outdated.png View File


+ 7 - 0
tracim/tracim/templates/master_authenticated.mak View File

113
                                 </ul>
113
                                 </ul>
114
                             </li>
114
                             </li>
115
                         % endif
115
                         % endif
116
+                        
117
+                        <form class="navbar-form navbar-left" role="search" action="${tg.url('/search?')}">
118
+                            <div class="form-group">
119
+                                <input type="text" class="form-control" placeholder="${_('Search for...')}" name="keywords" value="${','.join(search.keywords) if search else ''}">
120
+                            </div>
121
+                            <button type="submit" class="btn btn-default">${_('Search')}</button>
122
+                        </form>
116
                     </ul>
123
                     </ul>
117
                 % endif
124
                 % endif
118
 
125
 

+ 77 - 0
tracim/tracim/templates/search.mak View File

1
+<%inherit file="local:templates.master_authenticated_left_treeview_right_toolbar"/>
2
+
3
+<%namespace name="TIM" file="tracim.templates.pod"/>
4
+<%namespace name="TOOLBAR" file="tracim.templates.search_toolbars"/>
5
+<%namespace name="FORMS" file="tracim.templates.user_workspace_forms"/>
6
+<%namespace name="WIDGETS" file="tracim.templates.user_workspace_widgets"/>
7
+
8
+<%def name="title()">${_('My workspaces')}</%def>
9
+
10
+<%def name="SIDEBAR_LEFT_CONTENT()">
11
+    <h4>${_('Workspaces')}</h4>
12
+    ${WIDGETS.TREEVIEW('sidebar-left-menu', '__')}
13
+    <hr/>
14
+</%def>
15
+
16
+<%def name="SIDEBAR_RIGHT_CONTENT()">
17
+    ${TOOLBAR.SECURED_SEARCH(fake_api.current_user)}
18
+</%def>
19
+
20
+<%def name="REQUIRED_DIALOGS()">
21
+</%def>
22
+
23
+############################################################################
24
+##
25
+## PAGE CONTENT BELOW
26
+##
27
+############################################################################
28
+
29
+<div class="row">
30
+    <h1 class="page-header">
31
+        ${TIM.ICO(32, 'actions/system-search')} ${_('Search results')}
32
+        <small>
33
+            ${_('for keywords: ')|n}
34
+            % for keyword in search.keywords:
35
+                <span class="label label-default">${keyword}</span>
36
+            % endfor
37
+        </small>
38
+    </h1>
39
+</div>
40
+<div class="row">
41
+    <p id="search-result-dynamic-resume">${_('loading...')}</p>
42
+</div>
43
+<div class="row">
44
+    <div id='application-document-panel'>
45
+        <ol class="search-results">
46
+            % for item in search.results:
47
+                <li class="search-result-type-${item.type} search-result-status-${item.status.id}">
48
+                    <h4>
49
+                        <a href="${item.breadcrumb[-1].url}">${TIM.ICO(16, item.icon)} ${item.label}</a>
50
+                         &nbsp;&nbsp;<span style="color: #AAA;">—</span>
51
+                         <button type="button" class="btn btn-default btn-disabled btn-link ">
52
+                             ${TIM.ICO(16, item['status']['icon'])}&nbsp;
53
+                             <span class="${item.status.css}">${item.status.label}</span>
54
+                         </button>
55
+                    </h4>
56
+                    <p style="margin-bottom: 2em;" class="search-result-item-breadcrumb">
57
+                        <i style="color: #AAA;" class="fa fa-fw fa-map-marker"></i>
58
+                        % for bread in item.breadcrumb:
59
+                            / <a href="${bread.url}">${bread.label}</a>
60
+                        % endfor
61
+                        <br/>
62
+                        <span
63
+                            style="color: #AAA;"
64
+##                            rel="tooltip"
65
+##                            data-toggle="tooltip"
66
+##                            data-placement="top"
67
+                            title="${_('Last known activty')}" ><i class="fa fa-fw fa-calendar"></i> ${h.date_time_in_long_format(item.last_activity, '%d %B %Y at %I:%M')}</span> &mdash;
68
+                            ${h.shorten(item.content_raw, 300)}
69
+                    </p>
70
+
71
+##                    <hr style="width: 33%; margin-left: 0;"/>
72
+                </li>
73
+            % endfor
74
+        </ol>
75
+    </div>
76
+</div>
77
+

+ 83 - 0
tracim/tracim/templates/search_toolbars.mak View File

1
+<%namespace name="TIM" file="tracim.templates.pod"/>
2
+
3
+<%def name="SECURED_SEARCH(user)">
4
+    <%
5
+        content_types = [
6
+            ('page', 'mimetypes/text-html'),
7
+            ('file', 'status/mail-attachment'),
8
+            ('thread', 'apps/internet-group-chat'),
9
+            ('folder', 'places/jstree-folder')
10
+        ]
11
+
12
+        statuses = [
13
+            ('open', 'status/status-open'),
14
+            ('closed-validated', 'emblems/emblem-checked'),
15
+            ('closed-cancelled', 'emblems/emblem-unreadable'),
16
+            ('closed-deprecated', 'status/status-outdated')
17
+        ]
18
+    %>
19
+    <div class="btn-group btn-group-vertical" data-toggle="buttons">
20
+        % for content_type, icon in content_types:
21
+            <label class="btn btn-default active search-result-filter-button" id='show-hide-search-result-of-type-${content_type}'>
22
+                <input type="checkbox" autocomplete="off" checked> ${TIM.ICO(32, icon)}
23
+            </label>
24
+        % endfor
25
+    </div>
26
+    <p></p>
27
+    <div class="btn-group btn-group-vertical" data-toggle="buttons">
28
+        % for status, icon in statuses:
29
+            <label class="btn btn-default active search-result-filter-button" id='show-hide-search-result-with-status-${status}'>
30
+                <input type="checkbox" autocomplete="off" checked> ${TIM.ICO(32, icon)}
31
+            </label>
32
+        % endfor
33
+    </div>
34
+    <p></p>
35
+    
36
+    <script>
37
+        $(document).ready(function() {
38
+            % for content_type, icon in content_types: # python code
39
+                $('#show-hide-search-result-of-type-${content_type}').click(function() {
40
+                    if ($('#show-hide-search-result-of-type-${content_type}').hasClass('active')) {
41
+                        $('.search-result-type-${content_type}').hide();
42
+                    } else {
43
+                        $('.search-result-type-${content_type}').show();
44
+                    }
45
+                });
46
+            % endfor # python code
47
+
48
+            % for status, icon in statuses: # python code
49
+                $('#show-hide-search-result-with-status-${status}').click(function() {
50
+                    console.log('clieck')
51
+                    if ($('#show-hide-search-result-with-status-${status}').hasClass('active')) {
52
+                        $('.search-result-status-${status}').hide();
53
+                    } else {
54
+                        $('.search-result-status-${status}').show();
55
+                    }
56
+                });
57
+            % endfor # python code
58
+            
59
+            function refresh_search_result_count() {
60
+                var itemNb = $('ol.search-results > li').length;
61
+                var visibleItemNb = $('ol.search-results > li:visible').length;
62
+                var message = "${_('Showing {0} filtered items of {1}')}"
63
+                if(visibleItemNb<=1) {
64
+                    message = "${_('Showing {0} filtered item of a total of {1}')}"
65
+                } else if (visibleItemNb==itemNb) {
66
+                    message = "${_('Showing all items. You can filter by clicking right toolbar buttons.')}"
67
+                }
68
+
69
+                message = message.replace('{0}', visibleItemNb)
70
+                message = message.replace('{1}', itemNb)
71
+                $('#search-result-dynamic-resume').html(message);
72
+            }
73
+
74
+            $('.search-result-filter-button').click(function() {
75
+                refresh_search_result_count();
76
+            });
77
+            
78
+            refresh_search_result_count();
79
+        });
80
+    </script>
81
+
82
+</%def>
83
+

+ 75 - 3
tracim/tracim/tests/library/test_content_api.py View File

5
 
5
 
6
 import transaction
6
 import transaction
7
 
7
 
8
+from tracim.lib.base import logger
8
 from tracim.lib.content import compare_content_for_sorting_by_type_and_name
9
 from tracim.lib.content import compare_content_for_sorting_by_type_and_name
9
 from tracim.lib.content import ContentApi
10
 from tracim.lib.content import ContentApi
10
 from tracim.lib.user import UserApi
11
 from tracim.lib.user import UserApi
187
 
188
 
188
         api = ContentApi(user)
189
         api = ContentApi(user)
189
         p = api.create(ContentType.Page, None, None, 'this_is_a_page')
190
         p = api.create(ContentType.Page, None, None, 'this_is_a_page')
190
-        transaction.commit()
191
-
192
         c = api.create_comment(None, p, 'this is the comment', True)
191
         c = api.create_comment(None, p, 'this is the comment', True)
193
-        transaction.commit()
194
 
192
 
195
         eq_(Content, c.__class__)
193
         eq_(Content, c.__class__)
196
         eq_(p.content_id, c.parent_id)
194
         eq_(p.content_id, c.parent_id)
377
         eq_(False, updated2.is_deleted)
375
         eq_(False, updated2.is_deleted)
378
         eq_(ActionDescription.UNDELETION, updated2.revision_type)
376
         eq_(ActionDescription.UNDELETION, updated2.revision_type)
379
         eq_(u1id, updated2.owner_id)
377
         eq_(u1id, updated2.owner_id)
378
+
379
+    def test_search_in_label(self):
380
+        # HACK - D.A. - 2015-03-09
381
+        # This test is based on a bug which does NOT return results found
382
+        # at root of a workspace (eg a folder)
383
+        uapi = UserApi(None)
384
+        user = uapi.create_user()
385
+        user.email = 'this.is@user'
386
+        uapi.save(user)
387
+
388
+        api = ContentApi(user)
389
+
390
+        a = api.create(ContentType.Folder, None, None, 'this is randomized folder', True)
391
+        p = api.create(ContentType.Page, None, a, 'this is randomized label content', True)
392
+        p.description = 'This is some amazing test'
393
+        api.save(p)
394
+        original_id = p.content_id
395
+
396
+        res = api.search(['randomized'])
397
+        eq_(1, len(res.all()))
398
+        item = res.all()[0]
399
+        eq_(original_id, item.content_id)
400
+
401
+    def test_search_in_description(self):
402
+        # HACK - D.A. - 2015-03-09
403
+        # This test is based on a bug which does NOT return results found
404
+        # at root of a workspace (eg a folder)
405
+        uapi = UserApi(None)
406
+        user = uapi.create_user()
407
+        user.email = 'this.is@user'
408
+        uapi.save(user)
409
+
410
+        api = ContentApi(user)
411
+
412
+        a = api.create(ContentType.Folder, None, None, 'this is randomized folder', True)
413
+
414
+        p = api.create(ContentType.Page, None, a, 'this is dummy label content', True)
415
+        p.description = 'This is some amazing test'
416
+        api.save(p)
417
+        original_id = p.content_id
418
+
419
+        res = api.search(['dummy'])
420
+        eq_(1, len(res.all()))
421
+        item = res.all()[0]
422
+        eq_(original_id, item.content_id)
423
+
424
+
425
+    def test_search_in_label_or_description(self):
426
+        # HACK - D.A. - 2015-03-09
427
+        # This test is based on a bug which does NOT return results found
428
+        # at root of a workspace (eg a folder)
429
+        uapi = UserApi(None)
430
+        user = uapi.create_user()
431
+        user.email = 'this.is@user'
432
+        uapi.save(user)
433
+
434
+        api = ContentApi(user)
435
+
436
+        a = api.create(ContentType.Folder, None, None, 'this is randomized folder', True)
437
+        p1 = api.create(ContentType.Page, None, a, 'this is dummy label content', True)
438
+        p1.description = 'This is some amazing test'
439
+        p2 = api.create(ContentType.Page, None, a, 'Hey ! Jon !', True)
440
+        p2.description = 'What\'s up ?'
441
+        api.save(p1)
442
+        api.save(p2)
443
+
444
+        id1 = p1.content_id
445
+        id2 = p2.content_id
446
+
447
+        res = api.search(['dummy', 'jon'])
448
+        eq_(2, len(res.all()))
449
+
450
+        eq_(True, id1 in [o.content_id for o in res.all()])
451
+        eq_(True, id2 in [o.content_id for o in res.all()])