浏览代码

implement jstree menu with ajax dynamic loading

Damien Accorsi 11 年前
父节点
当前提交
f5981aadff

+ 8 - 48
pboard/pboard/controllers/api.py 查看文件

24
 from pboard.lib   import dbapi as pld
24
 from pboard.lib   import dbapi as pld
25
 from pboard.model import data as pmd
25
 from pboard.model import data as pmd
26
 from pboard.model import auth as pma
26
 from pboard.model import auth as pma
27
+from pboard.model import serializers as pms
27
 from pboard import model as pm
28
 from pboard import model as pm
28
 from pboard.lib.auth import can_read, can_write
29
 from pboard.lib.auth import can_read, can_write
29
 
30
 
30
-__all__ = ['PODPublicApiController', 'PODApiController']
31
+from pboard.controllers import apimenu as pcam
31
 
32
 
32
-FIXME_ERROR_CODE=-1
33
-
34
-class PODPublicApiController(BaseController):
35
-
36
-    @expose()
37
-    def create_account(self, email='', password='', retyped_password='', **kw):
38
-      if email=='' or password=='' or retyped_password=='':
39
-        flash(_('Account creation error: please fill all the fields'), 'error')
40
-        redirect(lurl('/'))
41
-      elif password!=retyped_password:
42
-        flash(_('Account creation error: passwords do not match'), 'error')
43
-        redirect(lurl('/'))
44
-      else:
45
-        loExistingUser = pld.PODStaticController.getUserByEmailAddress(email)
46
-        if loExistingUser!=None:
47
-          flash(_('Account creation error: account already exist: %s') % (email), 'error')
48
-          redirect(lurl('/'))
49
-        
50
-        loNewAccount = pld.PODStaticController.createUser()
51
-        loNewAccount.email_address = email
52
-        loNewAccount.display_name  = email
53
-        loNewAccount.password      = password
54
-
55
-        loUserGroup = pld.PODStaticController.getGroup('user')
56
-        loUserGroup.users.append(loNewAccount)
57
-
58
-        pm.DBSession.add(loNewAccount)
59
-        pm.DBSession.flush()
60
-        pm.DBSession.refresh(loNewAccount)
61
-
62
-        loUserSpecificGroup = pld.PODStaticController.createGroup()
63
 
33
 
64
-        loUserSpecificGroup.group_id = 0-loNewAccount.user_id # group id of a given user is the opposite of the user id
65
-        loUserSpecificGroup.group_name = 'user_%d' % loNewAccount.user_id
66
-        loUserSpecificGroup.personnal_group = True
67
-        loUserSpecificGroup.users.append(loNewAccount)
68
-
69
-        pm.DBSession.flush()
70
-
71
-        flash(_('Account successfully created: %s') % (email), 'info')
72
-        redirect(lurl('/'))
34
+FIXME_ERROR_CODE=-1
73
 
35
 
74
 
36
 
75
 class PODApiController(BaseController):
37
 class PODApiController(BaseController):
76
     """Sample controller-wide authorization"""
38
     """Sample controller-wide authorization"""
77
     
39
     
78
     allow_only = tgp.in_group('user', msg=l_('You need to login in order to access this ressource'))
40
     allow_only = tgp.in_group('user', msg=l_('You need to login in order to access this ressource'))
79
-    
41
+
42
+    menu = pcam.PODApiMenuController()
43
+
80
     @expose()
44
     @expose()
81
     def create_event(self, parent_id=None, data_label='', data_datetime=None, data_content='', data_reminder_datetime=None, add_reminder=False, **kw):
45
     def create_event(self, parent_id=None, data_label='', data_datetime=None, data_content='', data_reminder_datetime=None, add_reminder=False, **kw):
82
       loCurrentUser   = pld.PODStaticController.getCurrentUser()
46
       loCurrentUser   = pld.PODStaticController.getCurrentUser()
170
         loImage     = pil.open(loJpegBytes)
134
         loImage     = pil.open(loJpegBytes)
171
         loImage.thumbnail([140,140], pil.ANTIALIAS)
135
         loImage.thumbnail([140,140], pil.ANTIALIAS)
172
         
136
         
173
-        loResultBuffer = StringIO()
137
+        loResultBuffer = csio.StringIO()
174
         loImage.save(loResultBuffer,"JPEG")
138
         loImage.save(loResultBuffer,"JPEG")
175
         tg.response.headers['Content-type'] = str(loFile.data_file_mime_type)
139
         tg.response.headers['Content-type'] = str(loFile.data_file_mime_type)
176
         return loResultBuffer.getvalue()
140
         return loResultBuffer.getvalue()
356
       loNode = loApiController.getNode(node_id)
320
       loNode = loApiController.getNode(node_id)
357
 
321
 
358
       is_shared_b = False if is_shared=='off' else True
322
       is_shared_b = False if is_shared=='off' else True
359
-      print(is_shared_b)
360
-      print(loNode.is_shared)
361
-      print(loNode.owner_id)
362
-      print(loCurrentUser.user_id)
323
+
363
 
324
 
364
       # Only the node owner can modify is_shared
325
       # Only the node owner can modify is_shared
365
       if is_shared_b != loNode.is_shared and loNode.owner_id != loCurrentUser.user_id:
326
       if is_shared_b != loNode.is_shared and loNode.owner_id != loCurrentUser.user_id:
407
             comment_right.rights = liRightLevel
368
             comment_right.rights = liRightLevel
408
 
369
 
409
       redirect(lurl('/document/%s#tab-accessmanagement'%(loNode.node_id)))
370
       redirect(lurl('/document/%s#tab-accessmanagement'%(loNode.node_id)))
410
-

+ 52 - 0
pboard/pboard/controllers/apimenu.py 查看文件

1
+# -*- coding: utf-8 -*-
2
+
3
+import tg
4
+
5
+from pboard.lib import base as plb
6
+from pboard.lib import dbapi as pld
7
+from pboard.model import data as pmd
8
+from pboard.model import serializers as pms
9
+
10
+class PODApiMenuController(plb.BaseController):
11
+
12
+    @tg.expose('json')
13
+    @pms.PBNodeForMenu
14
+    def children(self, id='#'):
15
+        try:
16
+            real_id = int(id)
17
+        except Exception:
18
+            real_id = None
19
+
20
+        current_user = pld.PODStaticController.getCurrentUser()
21
+        api_controller = pld.PODUserFilteredApiController(current_user.user_id)
22
+
23
+        allowed_nodes = api_controller.getListOfAllowedNodes()
24
+        parent_node = api_controller.getNode(real_id)
25
+        child_nodes = api_controller.getChildNodesForMenu(parent_node, allowed_nodes)
26
+
27
+        return child_nodes
28
+
29
+    @tg.expose('json')
30
+    @pms.NodeTreeItemForMenu
31
+    def initialize(self, current_node_id=0, id='#'):
32
+        try:
33
+            real_id = int(id)
34
+        except Exception:
35
+            real_id = None
36
+
37
+        current_user = pld.PODStaticController.getCurrentUser()
38
+        api_controller = pld.PODUserFilteredApiController(current_user.user_id)
39
+
40
+        current_node = None
41
+        try:
42
+            current_node = api_controller.getNode(current_node_id)
43
+        except:
44
+            print("Node not found: {0}".format(current_node_id))
45
+
46
+        allowed_nodes = api_controller.getListOfAllowedNodes()
47
+        initial_menu_structure = api_controller.buildTreeListForMenu(current_node, pmd.PBNodeStatus.getVisibleIdsList(), allowed_nodes)
48
+
49
+        return initial_menu_structure
50
+
51
+
52
+

+ 51 - 0
pboard/pboard/controllers/apipublic.py 查看文件

1
+# -*- coding: utf-8 -*-
2
+
3
+import tg
4
+from tg import _compat
5
+from pboard.lib import base as plb
6
+from pboard.lib import dbapi as pld
7
+from pboard import model as pm
8
+from pboard.model import data as pmd
9
+from pboard.model import serializers as pms
10
+
11
+from tg.i18n import ugettext as _
12
+
13
+class PODPublicApiController(plb.BaseController):
14
+
15
+    @tg.expose()
16
+    def create_account(self, email='', password='', retyped_password='', **kw):
17
+      if email=='' or password=='' or retyped_password=='':
18
+        tg.flash(_('Account creation error: please fill all the fields'), 'error')
19
+        tg.redirect(tg.lurl('/'))
20
+      elif password!=retyped_password:
21
+        tg.flash(_('Account creation error: passwords do not match'), 'error')
22
+        tg.redirect(tg.lurl('/'))
23
+      else:
24
+        loExistingUser = pld.PODStaticController.getUserByEmailAddress(email)
25
+        if loExistingUser!=None:
26
+          tg.flash(_('Account creation error: account already exist: %s') % (email), 'error')
27
+          tg.redirect(tg.lurl('/'))
28
+
29
+        loNewAccount = pld.PODStaticController.createUser()
30
+        loNewAccount.email_address = email
31
+        loNewAccount.display_name  = email
32
+        loNewAccount.password      = password
33
+
34
+        loUserGroup = pld.PODStaticController.getGroup('user')
35
+        loUserGroup.users.append(loNewAccount)
36
+
37
+        pm.DBSession.add(loNewAccount)
38
+        pm.DBSession.flush()
39
+        pm.DBSession.refresh(loNewAccount)
40
+
41
+        loUserSpecificGroup = pld.PODStaticController.createGroup()
42
+
43
+        loUserSpecificGroup.group_id = 0-loNewAccount.user_id # group id of a given user is the opposite of the user id
44
+        loUserSpecificGroup.group_name = 'user_%d' % loNewAccount.user_id
45
+        loUserSpecificGroup.personnal_group = True
46
+        loUserSpecificGroup.users.append(loNewAccount)
47
+
48
+        pm.DBSession.flush()
49
+
50
+        tg.flash(_('Account successfully created: %s') % (email), 'info')
51
+        tg.redirect(tg.lurl('/'))

+ 4 - 5
pboard/pboard/controllers/root.py 查看文件

15
 from pboard.controllers.error import ErrorController
15
 from pboard.controllers.error import ErrorController
16
 
16
 
17
 from pboard.lib import dbapi as pld
17
 from pboard.lib import dbapi as pld
18
-from pboard.controllers import api as pbca
18
+from pboard.controllers import api as pca
19
+from pboard.controllers import apipublic as pcap
19
 from pboard.controllers import debug as pbcd
20
 from pboard.controllers import debug as pbcd
20
 
21
 
21
 from pboard import model as pm
22
 from pboard import model as pm
46
         config_type = tgat.TGAdminConfig
47
         config_type = tgat.TGAdminConfig
47
     )
48
     )
48
 
49
 
49
-    api   = pbca.PODApiController()
50
+    api   = pca.PODApiController()
50
     debug = pbcd.DebugController()
51
     debug = pbcd.DebugController()
51
     error = ErrorController()
52
     error = ErrorController()
52
 
53
 
53
-    public_api = pbca.PODPublicApiController()
54
+    public_api = pcap.PODPublicApiController()
54
 
55
 
55
     def _before(self, *args, **kw):
56
     def _before(self, *args, **kw):
56
         tmpl_context.project_name = "pboard"
57
         tmpl_context.project_name = "pboard"
143
         # FIXME - D.A - 2013-11-07 - Currently, the code build a new item if no item found for current user
144
         # FIXME - D.A - 2013-11-07 - Currently, the code build a new item if no item found for current user
144
         # the correct behavior should be to redirect to setup page
145
         # the correct behavior should be to redirect to setup page
145
         loMenuItems = loApiController.buildTreeListForMenu(loCurrentNode, pbmd.PBNodeStatus.getVisibleIdsList(), llAccessibleNodes)
146
         loMenuItems = loApiController.buildTreeListForMenu(loCurrentNode, pbmd.PBNodeStatus.getVisibleIdsList(), llAccessibleNodes)
146
-        nodes_as_tree_for_select_field = loApiController.DIRTY_OLDbuildTreeListForMenu(pbmd.PBNodeStatus.getVisibleIdsList())
147
 
147
 
148
         return dict(
148
         return dict(
149
             menu_node_list=loMenuItems,
149
             menu_node_list=loMenuItems,
150
-            root_node_list_for_select_field=nodes_as_tree_for_select_field,
151
             current_node=loCurrentNode,
150
             current_node=loCurrentNode,
152
             node_status_list = loNodeStatusList,
151
             node_status_list = loNodeStatusList,
153
             keywords = highlight,
152
             keywords = highlight,

+ 125 - 21
pboard/pboard/lib/dbapi.py 查看文件

9
 from sqlalchemy.types import Unicode, Integer, DateTime, Text
9
 from sqlalchemy.types import Unicode, Integer, DateTime, Text
10
 from sqlalchemy.orm import relation, synonym
10
 from sqlalchemy.orm import relation, synonym
11
 from sqlalchemy.orm import joinedload_all
11
 from sqlalchemy.orm import joinedload_all
12
+import sqlalchemy.orm as sqlao
12
 import sqlalchemy as sqla
13
 import sqlalchemy as sqla
13
 
14
 
14
 from pboard.model import DeclarativeBase, metadata, DBSession
15
 from pboard.model import DeclarativeBase, metadata, DBSession
82
     self._iCurrentUserId       = piUserId
83
     self._iCurrentUserId       = piUserId
83
     self._iExtraUserIdList     = piExtraUserIdList
84
     self._iExtraUserIdList     = piExtraUserIdList
84
     self._iUserIdFilteringList = None
85
     self._iUserIdFilteringList = None
85
-  
86
+    self._cache_allowed_nodes = None
86
 
87
 
87
   def _getUserIdListForFiltering(self):
88
   def _getUserIdListForFiltering(self):
88
     if self._iUserIdFilteringList==None:
89
     if self._iUserIdFilteringList==None:
127
     lsNodeIdFiltering = lsSqlSelectQuery % (str(self._iCurrentUserId))
128
     lsNodeIdFiltering = lsSqlSelectQuery % (str(self._iCurrentUserId))
128
 
129
 
129
     if liNodeId!=None and liNodeId!=0:
130
     if liNodeId!=None and liNodeId!=0:
130
-      return DBSession.query(pbmd.PBNode).options(joinedload_all("_lAllChildren"))\
131
+      return DBSession.query(pbmd.PBNode).options(sqlao.joinedload_all("_oParent"), sqlao.joinedload_all("_lAllChildren"))\
131
         .filter(pbmd.PBNode.node_id==liNodeId)\
132
         .filter(pbmd.PBNode.node_id==liNodeId)\
132
         .filter(
133
         .filter(
133
           sqla.or_(
134
           sqla.or_(
146
     return DBSession.query(pbmd.PBNode).options(joinedload_all("_lAllChildren")).filter(pbmd.PBNode.owner_id.in_(liOwnerIdList)).order_by(pbmd.PBNode.updated_at.desc()).limit(piMaxNodeNb).all()
147
     return DBSession.query(pbmd.PBNode).options(joinedload_all("_lAllChildren")).filter(pbmd.PBNode.owner_id.in_(liOwnerIdList)).order_by(pbmd.PBNode.updated_at.desc()).limit(piMaxNodeNb).all()
147
 
148
 
148
 
149
 
149
-  def getListOfAllowedNodes(self) -> pbmd.PBNode:
150
-    lsSqlQuery = """
151
-        SELECT
152
-            pgn.node_id
153
-        FROM
154
-            pod_group_node AS pgn
155
-            join pod_user_group AS pug ON pug.group_id = pgn.group_id
156
-        WHERE
157
-            pgn.rights > 0
158
-            AND pug.user_id = :owner_id
159
-        UNION
150
+  def getListOfAllowedNodes(self, reset_cache=False) -> pbmd.PBNode:
151
+      if self._cache_allowed_nodes==None or reset_cache==True:
152
+        lsSqlQuery = """
160
             SELECT
153
             SELECT
161
-                node_id
154
+                pn.node_id,
155
+                pn.parent_id,
156
+                pn.node_order,
157
+                pn.node_type,
158
+                pn.created_at,
159
+                pn.updated_at,
160
+                pn.data_label,
161
+                pn.data_content,
162
+                pn.data_datetime,
163
+                pn.node_status,
164
+                pn.data_reminder_datetime,
165
+                pn.parent_tree_path,
166
+                pn.node_depth,
167
+                pn.owner_id,
168
+                pn.is_shared,
169
+                pn.is_public,
170
+                pn.public_url_key
162
             FROM
171
             FROM
163
-                pod_nodes
172
+                pod_group_node AS pgn
173
+                join pod_user_group AS pug ON pug.group_id = pgn.group_id
174
+                join pod_nodes AS pn ON pgn.node_id=pn.node_id
164
             WHERE
175
             WHERE
165
-            owner_id=:owner_id;
166
-    """
167
-
168
-    loNodeListResult = DBSession.query(pbmd.PBNode).from_statement(lsSqlQuery).params(owner_id=self._iCurrentUserId)
169
-
170
-    return loNodeListResult.all()
176
+                pn.node_type='data'
177
+                AND pn.node_status NOT IN ('deleted', 'closed')
178
+                AND pn.node_id=pgn.node_id
179
+                AND pgn.rights > 0
180
+                AND pug.user_id = 3
181
+
182
+            UNION
183
+                SELECT
184
+                    pn.node_id,
185
+                    pn.parent_id,
186
+                    pn.node_order,
187
+                    pn.node_type,
188
+                    pn.created_at,
189
+                    pn.updated_at,
190
+                    pn.data_label,
191
+                    pn.data_content,
192
+                    pn.data_datetime,
193
+                    pn.node_status,
194
+                    pn.data_reminder_datetime,
195
+                    pn.parent_tree_path,
196
+                    pn.node_depth,
197
+                    pn.owner_id,
198
+                    pn.is_shared,
199
+                    pn.is_public,
200
+                    pn.public_url_key
201
+                FROM
202
+                    pod_nodes AS pn
203
+                WHERE
204
+                    pn.node_type='data'
205
+                    AND pn.node_status NOT IN ('deleted', 'closed')
206
+                    AND pn.owner_id=:owner_id
207
+
208
+            ORDER BY node_id ASC
209
+        """
210
+
211
+        loNodeListResult = DBSession.query(pbmd.PBNode).\
212
+            from_statement(lsSqlQuery).\
213
+            params(owner_id=self._iCurrentUserId)
214
+        allowed_nodes = loNodeListResult.all()
215
+
216
+        self._cache_allowed_nodes = allowed_nodes
217
+
218
+      return self._cache_allowed_nodes
171
 
219
 
172
 
220
 
173
   def searchNodesByText(self, plKeywordList: [str], piMaxNodeNb=100):
221
   def searchNodesByText(self, plKeywordList: [str], piMaxNodeNb=100):
197
     return DBSession.query(pbmd.PBNode).options(joinedload_all("_lAllChildren")).filter(pbmd.PBNode.owner_id.in_(liOwnerIdList)).filter(pbmd.PBNode.node_status==psNodeStatus).order_by(pbmd.PBNode.updated_at).limit(piMaxNodeNb).all()
245
     return DBSession.query(pbmd.PBNode).options(joinedload_all("_lAllChildren")).filter(pbmd.PBNode.owner_id.in_(liOwnerIdList)).filter(pbmd.PBNode.node_status==psNodeStatus).order_by(pbmd.PBNode.updated_at).limit(piMaxNodeNb).all()
198
 
246
 
199
 
247
 
248
+  def getChildNodesForMenu(self, poParentNode: pbmd.PBNode, allowed_nodes: [pbmd.PBNode]) -> [pbmd.PBNode]:
249
+    visible_child_nodes = []
250
+
251
+    if poParentNode!=None:
252
+        # standard case
253
+        print(" -------------- BEFORE @@@@@@@@@@@@@@@@ ")
254
+        all_child_nodes = poParentNode.getChildren()
255
+        print("@@@@@@@@@@@@@@@@ AFTER @@@@@@@@@@@@@@@@ ")
256
+        for child_node in all_child_nodes:
257
+          if child_node in allowed_nodes:
258
+            visible_child_nodes.append(child_node)
259
+    else:
260
+        # Root case
261
+        parent_nodes = DBSession
262
+        for allowed_node in allowed_nodes:
263
+            print("     @@@@@@@@@@@@@@@@ BEFORE @@@@@@@@@@@@@@@@ ")
264
+            # loParent = allowed_node._oParent
265
+            # print("@@@@@@@@@@@@@@@@ AFTER @@@@@@@@@@@@@@@@ {0}".format(allowed_node._oParent.node_id if allowed_node._oParent!=None else '0'))
266
+            # print("==== EXTRA {0}".format(allowed_node._oParent.node_id if allowed_node._oParent!=None else '0'))
267
+            print("====> ")
268
+
269
+            if allowed_node._oParent is None or allowed_node._oParent==allowed_node:
270
+                # D.A. - HACK - 2014-05-27
271
+                # I don't know why, but as from now (with use of sqlao.contains_eager in getListOfAllowedNodes()
272
+                # the root items have at first iteration itself as parent
273
+                print("==== EXTRA END")
274
+                # this is a root item => add it
275
+                visible_child_nodes.append(allowed_node)
276
+            else:
277
+                if allowed_node._oParent not in allowed_nodes:
278
+                    # the node is visible but not the parent => put it at the root
279
+                    visible_child_nodes.append(allowed_node)
280
+                else:
281
+                    print("==== EXTRA END 2 {0}".format(allowed_node._oParent.node_id))
282
+
283
+    print(" @@@@@@@@@@@@@@@@ PRE FAILURE @@@@@@@@@@@@@@@@ ")
284
+    return visible_child_nodes
285
+
286
+
287
+
288
+
200
   def buildTreeListForMenu(self, poCurrentNode: pbmd.PBNode, plViewableStatusId: [], plAllowedNodes: [pbmd.PBNode]) -> [pbmd.NodeTreeItem]:
289
   def buildTreeListForMenu(self, poCurrentNode: pbmd.PBNode, plViewableStatusId: [], plAllowedNodes: [pbmd.PBNode]) -> [pbmd.NodeTreeItem]:
201
     # The algorithm is:
290
     # The algorithm is:
202
     # 1. build an intermediate tree containing only current node and parent path
291
     # 1. build an intermediate tree containing only current node and parent path
209
     previous_tree_item = None
298
     previous_tree_item = None
210
     tmp_children_nodes = []
299
     tmp_children_nodes = []
211
 
300
 
301
+    print("============================> ",poCurrentNode)
212
     if poCurrentNode is not None:
302
     if poCurrentNode is not None:
213
         breadcrumb_nodes = poCurrentNode.getBreadCrumbNodes()
303
         breadcrumb_nodes = poCurrentNode.getBreadCrumbNodes()
304
+        print("====================> NODES", breadcrumb_nodes)
214
         breadcrumb_nodes.append(poCurrentNode) # by default the current node is not included
305
         breadcrumb_nodes.append(poCurrentNode) # by default the current node is not included
215
 
306
 
216
         for breadcrumb_node in reversed(breadcrumb_nodes):
307
         for breadcrumb_node in reversed(breadcrumb_nodes):
218
                 # First iteration. We add all current_node children
309
                 # First iteration. We add all current_node children
219
                 for child_node in breadcrumb_node.getChildren():
310
                 for child_node in breadcrumb_node.getChildren():
220
                     child_item = pbmd.NodeTreeItem(child_node, [])
311
                     child_item = pbmd.NodeTreeItem(child_node, [])
312
+                    assert(isinstance(child_item, pbmd.NodeTreeItem))
221
                     tmp_children_nodes.append(child_item)
313
                     tmp_children_nodes.append(child_item)
222
                 previous_tree_item = pbmd.NodeTreeItem(breadcrumb_node, tmp_children_nodes)
314
                 previous_tree_item = pbmd.NodeTreeItem(breadcrumb_node, tmp_children_nodes)
223
             else:
315
             else:
224
                 tmp_children_nodes = []
316
                 tmp_children_nodes = []
225
                 for child_node in breadcrumb_node.getChildren():
317
                 for child_node in breadcrumb_node.getChildren():
226
                     if child_node == previous_tree_item.node:
318
                     if child_node == previous_tree_item.node:
319
+                        assert(isinstance(previous_tree_item, pbmd.NodeTreeItem))
227
                         tmp_children_nodes.append(previous_tree_item)
320
                         tmp_children_nodes.append(previous_tree_item)
228
                     else:
321
                     else:
229
                         sibling_node = pbmd.NodeTreeItem(child_node, [])
322
                         sibling_node = pbmd.NodeTreeItem(child_node, [])
323
+                        assert(isinstance(sibling_node, pbmd.NodeTreeItem))
230
                         tmp_children_nodes.append(sibling_node)
324
                         tmp_children_nodes.append(sibling_node)
231
 
325
 
232
                 previous_tree_item = pbmd.NodeTreeItem(breadcrumb_node, tmp_children_nodes)
326
                 previous_tree_item = pbmd.NodeTreeItem(breadcrumb_node, tmp_children_nodes)
233
 
327
 
234
     for node in plAllowedNodes:
328
     for node in plAllowedNodes:
329
+        # This part of the algorithm insert root items
235
         if node.parent_id==0 or node.parent_id is None:
330
         if node.parent_id==0 or node.parent_id is None:
236
             if previous_tree_item is not None and node == previous_tree_item.node:
331
             if previous_tree_item is not None and node == previous_tree_item.node:
332
+                assert(isinstance(previous_tree_item, pbmd.NodeTreeItem))
237
                 node_tree.append(previous_tree_item)
333
                 node_tree.append(previous_tree_item)
238
             else:
334
             else:
239
                 node_tree.append(pbmd.NodeTreeItem(node, []))
335
                 node_tree.append(pbmd.NodeTreeItem(node, []))
336
+        else:
337
+            # Try to add nodes shared with me but with a parent which is not shared
338
+            if node.owner_id!=self._iCurrentUserId:
339
+                # this is a node shared with me
340
+                if node._oParent!=None and node._oParent not in plAllowedNodes:
341
+                    # the node is shared but not the parent => put it in the root
342
+                    node_tree.append(pbmd.NodeTreeItem(node))
343
+
240
 
344
 
241
     return node_tree
345
     return node_tree
242
 
346
 

+ 4 - 0
pboard/pboard/lib/helpers.py 查看文件

65
       return 'add-file-modal-form'
65
       return 'add-file-modal-form'
66
 
66
 
67
   @classmethod
67
   @classmethod
68
+  def MoveDocumentModalForm(cls, poNode):
69
+      return 'move-document-modal-form-{0}'.format(poNode.node_id)
70
+
71
+  @classmethod
68
   def AddEventModalForm(cls, poNode=None):
72
   def AddEventModalForm(cls, poNode=None):
69
     if poNode:
73
     if poNode:
70
       return 'add-event-modal-form-%d'%poNode.node_id
74
       return 'add-event-modal-form-%d'%poNode.node_id

+ 95 - 0
pboard/pboard/model/serializers.py 查看文件

1
+# -*- coding: utf-8 -*-
2
+import tg
3
+from pboard.model import data as pmd
4
+
5
+def PBNodeForMenu(func):
6
+
7
+    def process_item(item: pmd.PBNode):
8
+        """ convert given item into a dictionnary """
9
+        url = tg.url('/document/', dict(node_id=item.node_id)) ## FIXME - 2014-05-27 - Make this more flexible
10
+        print("########## BEFORE ##########")
11
+        new_item = dict(
12
+            id = item.node_id,
13
+            children = item.getChildNb()>0,
14
+            text = item.data_label,
15
+            # parent = item._oParent.node_id if (item._oParent!=None) else '#',
16
+            a_attr = { "href" : url },
17
+            li_attr = { "title": item.data_label }
18
+        )
19
+        print("########## AFTER ##########")
20
+        return new_item
21
+
22
+    def pre_serialize(*args, **kws):
23
+        initial_result = func(*args, **kws)
24
+        real_result = None
25
+
26
+        print("DEBUG ===================>", initial_result)
27
+        if isinstance(initial_result, list):
28
+            real_result = list()
29
+            for value_item in initial_result:
30
+                real_result.append(process_item(value_item))
31
+        else:
32
+            # We suppose here that we have an object only
33
+            real_result = process_item(initial_result)
34
+
35
+        return dict(d = real_result)
36
+
37
+    return pre_serialize
38
+
39
+
40
+def NodeTreeItemForMenu(func):
41
+    """ works with structure NodeTreeItem """
42
+    def process_item(structure_item: pmd.NodeTreeItem, current_node_id=None):
43
+        """ convert given item into a dictionnary """
44
+
45
+        item = structure_item.node
46
+        url = tg.url('/document/', dict(node_id=item.node_id)) ## FIXME - 2014-05-27 - Make this more flexible
47
+        children = []
48
+        for child_item in structure_item.children:
49
+            children.append(process_item(child_item, current_node_id))
50
+        # print("########## BEFORE ##########")
51
+
52
+        children_field_value = None
53
+        if len(children)>0:
54
+            children_field_value = children
55
+        elif item.getChildNb()>0:
56
+            children_field_value = True
57
+        else:
58
+            children_field_value = False
59
+
60
+        new_item_state = dict(
61
+            opened = len(children)>0,
62
+            selected = current_node_id!=None and item.node_id==current_node_id,
63
+        )
64
+
65
+        new_item = dict(
66
+            id = item.node_id,
67
+            children = children_field_value,
68
+            text = item.data_label,
69
+            # parent = item._oParent.node_id if (item._oParent!=None) else '#',
70
+            state = new_item_state,
71
+            a_attr = { "href" : url },
72
+            li_attr = { "title": item.data_label }
73
+        )
74
+        # print("########## AFTER ##########")
75
+        return new_item
76
+
77
+    def pre_serialize(*args, **kws):
78
+        initial_result = func(*args, **kws)
79
+        real_result = None
80
+
81
+        current_node_id = None
82
+        if "current_node_id" in kws:
83
+            current_node_id = int(kws['current_node_id'])
84
+
85
+        if isinstance(initial_result, list):
86
+            real_result = list()
87
+            for value_item in initial_result:
88
+                real_result.append(process_item(value_item, current_node_id))
89
+        else:
90
+            # We suppose here that we have an object only
91
+            real_result = process_item(initial_result, current_node_id)
92
+
93
+        return dict(d = real_result)
94
+
95
+    return pre_serialize

二进制
pboard/pboard/public/img/turbogears_logo.png 查看文件


二进制
pboard/pboard/public/img/turbogears_logo_big.png 查看文件


二进制
pboard/pboard/public/img/under_the_hood_blue.png 查看文件


+ 104 - 22
pboard/pboard/templates/document-widgets.mak 查看文件

24
   % endif
24
   % endif
25
 </%def>
25
 </%def>
26
 
26
 
27
-<%def name="ToolbarMenuItemModal(psTargetModalId, psIconClasses, psMenuLabel)">
28
-  <li><a href="#${psTargetModalId}" role="button" data-toggle="modal"><i class="${psIconClasses}"></i> ${psMenuLabel}</a></li>
27
+<%def name="ToolbarMenuItemModal(psTargetModalId, psIconClasses, psMenuLabel, psItemClasses='')">
28
+  <li class="${psItemClasses}"><a href="#${psTargetModalId}" role="button" data-toggle="modal"><i class="${psIconClasses}"></i> ${psMenuLabel}</a></li>
29
 </%def>
29
 </%def>
30
 
30
 
31
 <%def name="ToolbarMenuItemInline(psTargetId, psIconClasses, psMenuLabel)">
31
 <%def name="ToolbarMenuItemInline(psTargetId, psIconClasses, psMenuLabel)">
32
   <li><a href="#${psTargetId}"><i class="${psIconClasses}"></i> ${psMenuLabel}</a></li>
32
   <li><a href="#${psTargetId}"><i class="${psIconClasses}"></i> ${psMenuLabel}</a></li>
33
 </%def>
33
 </%def>
34
-<%def name="ToolbarMenuItemLink(psTargetUrl, psIconClasses, psMenuLabel, psLinkCss='', psLinkTitle='')">
34
+<%def name="ToolbarMenuItemLink(psTargetUrl, psIconClasses, psMenuLabel, psLinkCss='', psLinkTitle='', psOnClick='')">
35
   % if psTargetUrl=='#':
35
   % if psTargetUrl=='#':
36
-    <li class="disabled"><a href="${psTargetUrl}" class="${psLinkCss}" title="${psLinkTitle}"><i class="${psIconClasses}"></i> ${psMenuLabel}</a></li>
36
+    <li class="disabled"><a href="${psTargetUrl}" class="${psLinkCss}" title="${psLinkTitle}" onclick="${psOnClick}"><i class="${psIconClasses}"></i> ${psMenuLabel}</a></li>
37
   % else:
37
   % else:
38
-    <li><a href="${psTargetUrl}" class="${psLinkCss}" title="${psLinkTitle}"><i class="${psIconClasses}"></i> ${psMenuLabel}</a></li>
38
+    <li><a href="${psTargetUrl}" class="${psLinkCss}" title="${psLinkTitle}" onclick="${psOnClick}"><i class="${psIconClasses}"></i> ${psMenuLabel}</a></li>
39
   % endif
39
   % endif
40
 </%def>
40
 </%def>
41
 
41
 
44
   <div id="${psDivId}">
44
   <div id="${psDivId}">
45
     <div class="btn-group">
45
     <div class="btn-group">
46
       ${POD.EditButton('current-document-content-edit-button', True)}
46
       ${POD.EditButton('current-document-content-edit-button', True)}
47
+
47
       <button class="btn btn-small"  data-toggle="dropdown" href="#"> 
48
       <button class="btn btn-small"  data-toggle="dropdown" href="#"> 
48
         <i class="fa  fa-signal"></i>
49
         <i class="fa  fa-signal"></i>
49
         ${_("Change status")}
50
         ${_("Change status")}
72
       % endfor
73
       % endfor
73
       </ul>
74
       </ul>
74
     </div>
75
     </div>
76
+    
75
     <div class="btn-group">
77
     <div class="btn-group">
76
       <button class="btn btn-small btn-success"  data-toggle="dropdown" href="#">
78
       <button class="btn btn-small btn-success"  data-toggle="dropdown" href="#">
77
         <i class="fa fa-plus"></i> ${_('Add')}
79
         <i class="fa fa-plus"></i> ${_('Add')}
102
         <li><p class="pod-grey"><i class="fa fa-danger"></i> coming soon!</p></li>
104
         <li><p class="pod-grey"><i class="fa fa-danger"></i> coming soon!</p></li>
103
       </ul>
105
       </ul>
104
     </div>
106
     </div>
105
-    <div class="btn-group ">
106
-      <a
107
-        class="btn btn-small btn-warning"
108
-        href="#"
109
-        data-toggle="dropdown"
110
-        title="${_('Move to')}"
111
-        ><i class="fa fa-arrows"></i></a>
107
+
108
+    <div class="btn-group pull-right">
109
+      <button class="btn btn-small btn-link"  data-toggle="dropdown" href="#">
110
+        ${_('more ...')}
111
+      </button>
112
       <ul class="dropdown-menu">
112
       <ul class="dropdown-menu">
113
-        <li >
114
-          <div class="btn-warning strong" ><strong><i class="fa fa-magic"></i> ${_("Move the document...")}</strong><br/></div>
115
-          <div class="pod-grey"><i>move the document to...</i></div>
113
+        <li>
114
+          <div class="strong" ><strong><i class="fa fa-magic"></i> ${_('Advanced actions...')}</strong><br/></div>
115
+          <div class="pod-grey"><i>${_('power user actions...')}</i></div>
116
         </li>
116
         </li>
117
-        ${node_treeview_for_set_parent_menu(poNode.node_id, plRootNodes)}
117
+##
118
+## Here are MOVE and DELETE buttons
119
+##
120
+        ${ToolbarMenuItemModal(h.ID.MoveDocumentModalForm(current_node), 'fa fa-arrows', _('Move'), 'btn-warning')}
121
+        ${ToolbarMenuItemLink(tg.url('/api/edit_status', dict(node_id=poNode.node_id, node_status='deleted')), 'fa fa-trash-o', _('Delete'), 'btn-danger', _('Delete the current document'), 'return confirm(\'{0}\');'.format('Delete current document?'))}
122
+
118
       </ul>
123
       </ul>
119
-      <a
120
-        class="btn btn-small btn-danger"
121
-        href='${tg.url('/api/edit_status', dict(node_id=poNode.node_id, node_status='deleted'))}'
122
-        id='current-document-force-delete-button' onclick="return confirm('${_('Delete current document?')}');"
123
-        title="${_('Delete')}"
124
-        ><i class="fa fa-trash-o"></i></a>
125
     </div>
124
     </div>
126
   </div>
125
   </div>
127
 </%def>
126
 </%def>
537
     </div>
536
     </div>
538
   </div>
537
   </div>
539
 </%def>
538
 </%def>
539
+
540
+<%def name="MoveDocumentModalDialog(poNode, psPostUrl, psModalId, psTitle)">
541
+  <div
542
+    id="${psModalId}"
543
+    class="modal hide"
544
+    tabindex="-1"
545
+    role="dialog"
546
+    aria-labelledby="myModalLabel"
547
+    aria-hidden="true">
548
+    
549
+    <div class="modal-header">
550
+    ## MODAL HEADER
551
+      <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
552
+      <h3 id="myModalLabel">${psTitle}</h3>
553
+    ## MODAL HEADER [END]
554
+    </div>
555
+
556
+    <div class="modal-body">
557
+    ## MODAL BODY
558
+          <p>${_('Select the destination:')}</p>
559
+          <div id="${psModalId}-new-parent-selector-tree"></div>
560
+          <script>
561
+            $(function () {
562
+              $('#${psModalId}-new-parent-selector-tree').jstree({
563
+                "plugins" : [ "wholerow"],
564
+                'core' : {
565
+
566
+                  'error': function (error) {
567
+                    console.log('Error ' + error.toString())
568
+                  },
569
+                  'data' : {
570
+                    'dataType': 'json',
571
+                    'contentType': 'application/json; charset=utf-8',
572
+                    'url' : function (node) {
573
+                         return '${tg.url("/api/menu/children")}';
574
+                    },
575
+                    'data' : function(node) {
576
+                      console.log("NODE => "+JSON.stringify(node))
577
+                      return {
578
+                        'id' : node.id
579
+                      };
580
+                    },
581
+                    'success': function (new_data) {
582
+                      console.log('loaded new menu data' + new_data)
583
+                      return new_data;
584
+                    },
585
+                  },
586
+                }
587
+              });
588
+              
589
+              $('#${psModalId}-new-parent-selector-tree').on("select_node.jstree", function (e, data) {
590
+                new_parent_id_selected = data.selected[0];
591
+                $("#${psModalId}-form-new-parent-field").attr("value", new_parent_id_selected)
592
+                console.log("About to move document "+${poNode.node_id}+" as child of "+new_parent_id_selected);
593
+              });
594
+            });
595
+          </script>
596
+          <form id='${psModalId}-form' method="POST" action="${psPostUrl}" enctype="multipart/form-data">
597
+            <input type="hidden" name="node_id" value="${poNode.node_id}"/>
598
+            <input type="hidden" name="new_parent_id" value="-1" id="${psModalId}-form-new-parent-field" />
599
+          </form>
600
+    ## MODAL BODY [END]
601
+    </div>
602
+    
603
+    <div class="modal-footer">
604
+    ## MODAL FOOTER
605
+      <button class="btn" data-dismiss="modal" aria-hidden="true">
606
+        <i class="fa fa-ban"></i> ${_('Cancel')}
607
+      </button>
608
+      <button class="btn btn-success" id="${psModalId}-form-submit-button">
609
+        <i class="fa fa-check"></i> ${_('Save changes')}
610
+      </button>
611
+      <script>
612
+        $('#${psModalId}-form-submit-button').click(function(){
613
+          $('#${psModalId}-textarea-wysiwyg').cleanHtml();
614
+          $('#${psModalId}-textarea').val($('#${psModalId}-textarea-wysiwyg').html());
615
+          $('#${psModalId}-form')[0].submit();
616
+        });
617
+      </script>
618
+    ## MODAL FOOTER [END]
619
+    </div>
620
+  </div>
621
+</%def>

+ 54 - 5
pboard/pboard/templates/document.mak 查看文件

69
   </div>
69
   </div>
70
 
70
 
71
   <div class="row">
71
   <div class="row">
72
-    <div id='application-left-panel' class="span3">
73
-      <div>
74
-        ${node_treeview(menu_node_list)}
75
-      </div>
72
+    <div id='application-left-panel' class="span3" >
73
+      <link rel="stylesheet" href="${tg.url('/jstree/dist/themes/default/style.min.css')}" />
74
+      <script src="${tg.url('/jstree/dist/jstree.js')}"></script>
75
+      <style>
76
+        #mypodtree {overflow:hidden;}
77
+        #mypodtree:hover {overflow:visible; }
78
+      </style>
79
+      <div id="mypodtree"></div>
80
+      <script>
81
+        $(function () {
82
+          $('#mypodtree').jstree({
83
+            "plugins" : [ "wholerow"],
84
+            'core' : {
85
+
86
+              'error': function (error) {
87
+                console.log('Error ' + error.toString())
88
+              },
89
+              'data' : {
90
+                'dataType': 'json',
91
+                'contentType': 'application/json; charset=utf-8',
92
+                'url' : function (node) {
93
+                  if (node.id==='#') {
94
+                    return '${tg.url("/api/menu/initialize", dict(current_node_id=current_node.node_id if current_node else 0))}';
95
+                  } else {
96
+                    return '${tg.url("/api/menu/children")}';
97
+                  }
98
+                },
99
+                'data' : function(node) {
100
+                  console.log("NODE => "+JSON.stringify(node))
101
+                  return {
102
+                    'id' : node.id
103
+                  };
104
+                },
105
+                'success': function (new_data) {
106
+                  console.log('loaded new menu data' + new_data)
107
+                  return new_data;
108
+                },
109
+              },
110
+            }
111
+          });
112
+          
113
+          $('#mypodtree').on("select_node.jstree", function (e, data) {
114
+            url = "${tg.url('/document/')}"+data.selected[0];
115
+            console.log("Opening document: "+url);
116
+            location.href = url;
117
+          });
118
+        });
119
+      </script>
120
+## INFO - D.A. - 2014-05-28 - Hide old school menu
121
+##      <div>
122
+##        ${node_treeview(menu_node_list)}
123
+##      </div>
76
     </div>
124
     </div>
77
     <div id='application-main-panel' class="span9">
125
     <div id='application-main-panel' class="span9">
78
 
126
 
106
           ${DOC.EventEditModalDialog(current_node.node_id, None, tg.url('/api/create_event'), h.ID.AddEventModalForm(current_node), _('Add an event'))}
154
           ${DOC.EventEditModalDialog(current_node.node_id, None, tg.url('/api/create_event'), h.ID.AddEventModalForm(current_node), _('Add an event'))}
107
           ${DOC.ContactEditModalDialog(current_node.node_id, None, tg.url('/api/create_contact'), h.ID.AddContactModalForm(current_node), _('Add a new contact'))}
155
           ${DOC.ContactEditModalDialog(current_node.node_id, None, tg.url('/api/create_contact'), h.ID.AddContactModalForm(current_node), _('Add a new contact'))}
108
           ${DOC.FileEditModalDialog(current_node.node_id, None, tg.url('/api/create_file'), h.ID.AddFileModalForm(current_node), _('Add a new file'))}
156
           ${DOC.FileEditModalDialog(current_node.node_id, None, tg.url('/api/create_file'), h.ID.AddFileModalForm(current_node), _('Add a new file'))}
109
-          
157
+          ${DOC.MoveDocumentModalDialog(current_node, tg.url('/api/set_parent_node'), h.ID.MoveDocumentModalForm(current_node), _('Move the document'))}
158
+
110
           <div class="tabbable">
159
           <div class="tabbable">
111
             <ul class="nav nav-tabs" style="margin-bottom: 0em;">
160
             <ul class="nav nav-tabs" style="margin-bottom: 0em;">
112
                 <li>${DOC.MetadataTab('#subdocuments', 'tab', _('Subdocuments'), 'fa-file-text-o', current_node.getChildren())}</li>
161
                 <li>${DOC.MetadataTab('#subdocuments', 'tab', _('Subdocuments'), 'fa-file-text-o', current_node.getChildren())}</li>