Browse Source

bug #52: make the status list more coherent+ add automatic status for items containing tasks

damien 11 years ago
parent
commit
dd1f820436

+ 10 - 0
doc/database/pod-upgrade-0.1.0_to_0.2.0.sql View File

1
+
2
+--
3
+-- Remove useless status
4
+--
5
+UPDATE pod_nodes SET node_status='information' WHERE node_status='immortal';
6
+UPDATE pod_nodes SET node_status='inprogress' WHERE node_status='actiontodo';
7
+UPDATE pod_nodes SET node_status='inprogress' WHERE node_status='hot';
8
+UPDATE pod_nodes SET node_status='actiontodo' WHERE node_status='actiontodo';
9
+UPDATE pod_nodes SET node_status='closed' WHERE node_status='archived';
10
+

+ 2 - 2
pboard/pboard/controllers/root.py View File

123
         loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
123
         loApiController = pld.PODUserFilteredApiController(loCurrentUser.user_id)
124
 
124
 
125
         # loRootNodeList   = pbm.DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.parent_id==None).order_by(pbmd.PBNode.node_order).all()
125
         # loRootNodeList   = pbm.DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.parent_id==None).order_by(pbmd.PBNode.node_order).all()
126
-        loRootNodeList = loApiController.buildTreeListForMenu()
126
+        loRootNodeList = loApiController.buildTreeListForMenu(pbmd.PBNodeStatus.getVisibleIdsList())
127
         liNodeId         = int(node)
127
         liNodeId         = int(node)
128
         
128
         
129
         loCurrentNode    = None
129
         loCurrentNode    = None
130
         loNodeStatusList = None
130
         loNodeStatusList = None
131
         try:
131
         try:
132
-          loNodeStatusList = pbmd.PBNodeStatus.getList()
132
+          loNodeStatusList = pbmd.PBNodeStatus.getChoosableList()
133
           loCurrentNode    = loApiController.getNode(liNodeId)
133
           loCurrentNode    = loApiController.getNode(liNodeId)
134
         except Exception as e:
134
         except Exception as e:
135
           flash(_('Document not found'), 'error')
135
           flash(_('Document not found'), 'error')

+ 3 - 3
pboard/pboard/lib/dbapi.py View File

95
     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()
95
     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()
96
 
96
 
97
 
97
 
98
-  def buildTreeListForMenu(self):
98
+  def buildTreeListForMenu(self, plViewableStatusId):
99
     liOwnerIdList = self._getUserIdListForFiltering()
99
     liOwnerIdList = self._getUserIdListForFiltering()
100
     
100
     
101
-    loNodeList = pbm.DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.owner_id.in_(liOwnerIdList)).filter(pbmd.PBNode.node_type==pbmd.PBNodeType.Data).order_by(pbmd.PBNode.parent_tree_path).order_by(pbmd.PBNode.node_order).order_by(pbmd.PBNode.node_id).all()
101
+    loNodeList = pbm.DBSession.query(pbmd.PBNode).filter(pbmd.PBNode.owner_id.in_(liOwnerIdList)).filter(pbmd.PBNode.node_type==pbmd.PBNodeType.Data).filter(pbmd.PBNode.node_status.in_(plViewableStatusId)).order_by(pbmd.PBNode.parent_tree_path).order_by(pbmd.PBNode.node_order).order_by(pbmd.PBNode.node_id).all()
102
     loTreeList = []
102
     loTreeList = []
103
     loTmpDict = {}
103
     loTmpDict = {}
104
     for loNode in loNodeList:
104
     for loNode in loNodeList:
112
         # The following line may raise an exception
112
         # The following line may raise an exception
113
         # We suppose that the parent node has already been added
113
         # We suppose that the parent node has already been added
114
         # this *should* be the case, but the code does not check it
114
         # this *should* be the case, but the code does not check it
115
-        if loNode.parent_id in loTmpDict.keys():
115
+        if loNode.parent_id not in loTmpDict.keys():
116
           loTmpDict[loNode.parent_id] = self.getNode(loNode.parent_id)
116
           loTmpDict[loNode.parent_id] = self.getNode(loNode.parent_id)
117
         loTmpDict[loNode.parent_id].appendStaticChild(loNode)
117
         loTmpDict[loNode.parent_id].appendStaticChild(loNode)
118
   
118
   

+ 24 - 0
pboard/pboard/lib/helpers.py View File

5
 #from webhelpers import date, feedgenerator, html, number, misc, text
5
 #from webhelpers import date, feedgenerator, html, number, misc, text
6
 from markupsafe import Markup
6
 from markupsafe import Markup
7
 from datetime import datetime
7
 from datetime import datetime
8
+from tg.i18n import ugettext as _, lazy_ugettext as l_
8
 
9
 
9
 def current_year():
10
 def current_year():
10
   now = datetime.now()
11
   now = datetime.now()
15
         return Markup('<i class="icon-%s icon-white"></i>' % icon_name)
16
         return Markup('<i class="icon-%s icon-white"></i>' % icon_name)
16
     else:
17
     else:
17
         return Markup('<i class="icon-%s"></i>' % icon_name)
18
         return Markup('<i class="icon-%s"></i>' % icon_name)
19
+
20
+
21
+def getExplanationAboutStatus(psStatusId, psCurrentStatusId):
22
+  lsMsg = ""
23
+  if psStatusId==psCurrentStatusId:
24
+    return _("This is the current status.")
25
+  else:
26
+    if psStatusId=='information':
27
+      return _("The item is a normal document, like a howto or a text document.")
28
+    if psStatusId=='automatic':
29
+      return _("The item will be automatically computed as \"in progress\" or \"done\" according to its children status.")
30
+    if psStatusId=='new':
31
+      return _("No action done on the item.")
32
+    if psStatusId=='inprogress':
33
+      return _("The item is being worked on.")
34
+    if psStatusId=='standby':
35
+      return _("Waiting for some external actions.")
36
+    if psStatusId=='done':
37
+      return _("The work associated with the item is finished.")
38
+    if psStatusId=='closed':
39
+      return _("Close the item if you want not to see it anymore. The data won't be deleted")
40
+    if psStatusId=='deleted':
41
+      return _("This status tells that the item has been deleted.")

+ 56 - 19
pboard/pboard/model/data.py View File

5
 import datetime as datetimeroot
5
 import datetime as datetimeroot
6
 from datetime import datetime
6
 from datetime import datetime
7
 from hashlib import sha256
7
 from hashlib import sha256
8
-__all__ = ['User', 'Group', 'Permission']
9
 
8
 
10
 from sqlalchemy import Table, ForeignKey, Column, Sequence
9
 from sqlalchemy import Table, ForeignKey, Column, Sequence
11
 from sqlalchemy.types import Unicode, Integer, DateTime, Text, LargeBinary
10
 from sqlalchemy.types import Unicode, Integer, DateTime, Text, LargeBinary
12
 from sqlalchemy.orm import relation, synonym, relationship
11
 from sqlalchemy.orm import relation, synonym, relationship
13
 from sqlalchemy.orm import backref
12
 from sqlalchemy.orm import backref
13
+import sqlalchemy.orm as sqlao
14
 from sqlalchemy import orm as sqlao
14
 from sqlalchemy import orm as sqlao
15
 
15
 
16
+from tg.i18n import ugettext as _, lazy_ugettext as l_
17
+
16
 import tg
18
 import tg
17
 from pboard.model import DeclarativeBase, metadata, DBSession
19
 from pboard.model import DeclarativeBase, metadata, DBSession
18
 
20
 
92
 class PBNodeStatus(object):
94
 class PBNodeStatus(object):
93
     
95
     
94
   StatusList = dict()
96
   StatusList = dict()
95
-  StatusList['immortal']   = PBNodeStatusItem('immortal',   'Information',         'normal',    'fa fa-info-circle',        'pod-status-grey-light')
96
-  StatusList['new']        = PBNodeStatusItem('new',        'New',                 'open',      'fa fa-lightbulb-o',        'btn-success')
97
-  StatusList['inprogress'] = PBNodeStatusItem('inprogress', 'In progress',         'open',      'fa fa-gears fa-inverse',   'btn-info')
98
-  StatusList['actiontodo'] = PBNodeStatusItem('actiontodo', 'Action to do',        'open',      'fa fa-spinner fa-inverse', 'btn-info')
99
-  StatusList['standby']    = PBNodeStatusItem('standby',    'Waiting for news',    'open',      'fa fa-spinner fa-inverse', 'btn-warning')
100
-  StatusList['hot']        = PBNodeStatusItem('hot',        'Hot',                 'open',      'fa fa-warning fa-inverse', 'btn-danger')
101
-  StatusList['done']       = PBNodeStatusItem('done',       'Done',                'closed',    'fa fa-check-square-o',     'pod-status-grey-light')
102
-  StatusList['closed']     = PBNodeStatusItem('closed',     'Closed',              'closed',    'fa fa-lightbulb-o',        'pod-status-grey-middle')
103
-  StatusList['archived']   = PBNodeStatusItem('archived',   'Archived',            'invisible', 'fa fa-archive',            'pod-status-grey-dark')
104
-  StatusList['deleted']    = PBNodeStatusItem('deleted',    'Deleted',             'invisible', 'fa fa-trash-o',            'pod-status-grey-dark')
97
+  StatusList['information'] = PBNodeStatusItem('information', 'Information',         'normal', 'fa fa-info-circle',            'pod-status-grey-light')
98
+  StatusList['automatic']   = PBNodeStatusItem('automatic',   'Automatic',           'open',   'fa fa-flash',                  'pod-status-grey-light')
99
+  StatusList['new']         = PBNodeStatusItem('new',         'New',                 'open',   'fa fa-lightbulb-o fa-inverse', 'btn-success')
100
+  StatusList['inprogress']  = PBNodeStatusItem('inprogress',  'In progress',         'open',   'fa fa-gears fa-inverse',       'btn-info')
101
+  StatusList['standby']     = PBNodeStatusItem('standby',     'In standby',          'open',   'fa fa-spinner fa-inverse',     'btn-warning')
102
+  StatusList['done']        = PBNodeStatusItem('done',        'Done',                'closed', 'fa fa-check-square-o',         'pod-status-grey-light')
103
+  StatusList['closed']      = PBNodeStatusItem('closed',      'Closed',              'closed', 'fa fa-lightbulb-o',            'pod-status-grey-middle')
104
+  StatusList['deleted']     = PBNodeStatusItem('deleted',     'Deleted',             'closed', 'fa fa-trash-o',                'pod-status-grey-dark')
105
+
106
+  @classmethod
107
+  def getChoosableList(cls):
108
+    return [
109
+      PBNodeStatus.StatusList['information'],
110
+      PBNodeStatus.StatusList['automatic'],
111
+      PBNodeStatus.StatusList['new'],
112
+      PBNodeStatus.StatusList['inprogress'],
113
+      PBNodeStatus.StatusList['standby'],
114
+      PBNodeStatus.StatusList['done'],
115
+      PBNodeStatus.StatusList['closed'],
116
+    ]
117
+
118
+  @classmethod
119
+  def getVisibleIdsList(cls):
120
+    return ['information', 'automatic', 'new', 'inprogress', 'standby', 'done' ]
121
+
122
+  @classmethod
123
+  def getVisibleList(cls):
124
+    return [
125
+      PBNodeStatus.StatusList['information'],
126
+      PBNodeStatus.StatusList['automatic'],
127
+      PBNodeStatus.StatusList['new'],
128
+      PBNodeStatus.StatusList['inprogress'],
129
+      PBNodeStatus.StatusList['standby'],
130
+      PBNodeStatus.StatusList['done'],
131
+    ]
105
 
132
 
106
   @classmethod
133
   @classmethod
107
   def getList(cls):
134
   def getList(cls):
108
     return [
135
     return [
109
-      PBNodeStatus.StatusList['immortal'],
136
+      PBNodeStatus.StatusList['information'],
137
+      PBNodeStatus.StatusList['automatic'],
110
       PBNodeStatus.StatusList['new'],
138
       PBNodeStatus.StatusList['new'],
111
-      PBNodeStatus.StatusList['actiontodo'],
112
       PBNodeStatus.StatusList['inprogress'],
139
       PBNodeStatus.StatusList['inprogress'],
113
       PBNodeStatus.StatusList['standby'],
140
       PBNodeStatus.StatusList['standby'],
114
-      PBNodeStatus.StatusList['hot'],
115
       PBNodeStatus.StatusList['done'],
141
       PBNodeStatus.StatusList['done'],
116
       PBNodeStatus.StatusList['closed'],
142
       PBNodeStatus.StatusList['closed'],
117
-      PBNodeStatus.StatusList['archived'],
118
       PBNodeStatus.StatusList['deleted']
143
       PBNodeStatus.StatusList['deleted']
119
     ]
144
     ]
120
     
145
     
162
   parent_tree_path = Column(Unicode(255), unique=False, nullable=False, default='')
187
   parent_tree_path = Column(Unicode(255), unique=False, nullable=False, default='')
163
   owner_id         = Column(Integer, ForeignKey('pod_user.user_id'), nullable=True, default=None)
188
   owner_id         = Column(Integer, ForeignKey('pod_user.user_id'), nullable=True, default=None)
164
 
189
 
165
-  node_order  = Column(Integer, nullable=True, default=1)
166
-  node_type   = Column(Unicode(16), unique=False, nullable=False, default='data')
190
+  node_order   = Column(Integer, nullable=True, default=1)
191
+  node_type    = Column(Unicode(16), unique=False, nullable=False, default='data')
167
   node_status = Column(Unicode(16), unique=False, nullable=False, default='new')
192
   node_status = Column(Unicode(16), unique=False, nullable=False, default='new')
168
 
193
 
169
   created_at = Column(DateTime, unique=False, nullable=False)
194
   created_at = Column(DateTime, unique=False, nullable=False)
177
   
202
   
178
   data_file_name      = Column(Unicode(255),  unique=False, nullable=False, default='')
203
   data_file_name      = Column(Unicode(255),  unique=False, nullable=False, default='')
179
   data_file_mime_type = Column(Unicode(255),  unique=False, nullable=False, default='')
204
   data_file_mime_type = Column(Unicode(255),  unique=False, nullable=False, default='')
180
-  data_file_content   = Column(LargeBinary(), unique=False, nullable=False, default=None)
205
+  data_file_content   = sqlao.deferred(Column(LargeBinary(), unique=False, nullable=False, default=None))
181
 
206
 
182
 
207
 
183
   _oParent = relationship('PBNode', remote_side=[node_id], backref='_lAllChildren')
208
   _oParent = relationship('PBNode', remote_side=[node_id], backref='_lAllChildren')
280
     return poDateTime.strftime(psDateTimeFormat)
305
     return poDateTime.strftime(psDateTimeFormat)
281
 
306
 
282
   def getStatus(self):
307
   def getStatus(self):
283
-    return PBNodeStatus.getStatusItem(self.node_status)
308
+    loStatus = PBNodeStatus.getStatusItem(self.node_status)
309
+    if loStatus.status_id!='automatic':
310
+      return loStatus
311
+    else:
312
+      # Compute the status:
313
+      # - if at least one child is 'new' or 'in progress' or 'in standby' => status is inprogress
314
+      # - else if all status are 'done', 'closed' or 'deleted' => 'done'
315
+      lsRealStatusId = 'done'
316
+      for loChild in self.getChildren():
317
+        if loChild.getStatus().status_id in ('new', 'inprogress', 'standby'):
318
+          lsRealStatusId = 'inprogress'
319
+          break
320
+      return PBNodeStatus.getStatusItem(lsRealStatusId)
284
 
321
 
285
   def getTruncatedLabel(self, piCharNb):
322
   def getTruncatedLabel(self, piCharNb):
286
     lsTruncatedLabel = ''
323
     lsTruncatedLabel = ''

+ 23 - 13
pboard/pboard/templates/document.mak View File

95
       </div>
95
       </div>
96
     % endif
96
     % endif
97
       <div class="btn-group">
97
       <div class="btn-group">
98
-        <button class="btn">Status</button>
99
-        <a class="btn ${current_node.getStatus().css}" href="#"><i class="${current_node.getStatus().icon}"></i> ${current_node.getStatus().getLabel()}</a>
100
-        <a class="btn ${current_node.getStatus().css} dropdown-toggle" data-toggle="dropdown" href="#"><span class="caret"></span></a>
98
+        <button class="btn"  data-toggle="dropdown" href="#">${_("Change status")}</button>
99
+        <a class="btn dropdown-toggle" data-toggle="dropdown" href="#"><span class="caret"></span></a>
101
         <ul class="dropdown-menu">
100
         <ul class="dropdown-menu">
102
-          % for node_status in node_status_list:
103
-            <li>
104
-              <a class="${node_status.css}" href="${tg.url('/api/edit_status?node_id=%i&node_status=%s'%(current_node.node_id, node_status.status_id))}">
105
-                <i class="${node_status.icon_id}"></i> ${node_status.label}
106
-              </a>
107
-            </li>
108
-          % endfor
101
+        % for node_status in node_status_list:
102
+          % if node_status.status_id==current_node.getStatus().status_id:
103
+          <li title="${h.getExplanationAboutStatus(node_status.status_id, current_node.getStatus().status_id)}">
104
+            <a class="${node_status.css}" href="#"  style="color: #999;">
105
+              <i class="${node_status.icon_id}"></i> ${node_status.label}
106
+            </a>
107
+          </li>
108
+          % else:
109
+          <li title="${h.getExplanationAboutStatus(node_status.status_id, current_node.getStatus().status_id)}">
110
+            <a class="${node_status.css}" href="${tg.url('/api/edit_status?node_id=%i&node_status=%s'%(current_node.node_id, node_status.status_id))}">
111
+              <i class="${node_status.icon_id}"></i> ${node_status.label}
112
+            </a>
113
+          </li>
114
+          % endif
115
+        % endfor
109
         </ul>
116
         </ul>
110
       </div>
117
       </div>
111
       <div class="btn-group">
118
       <div class="btn-group">
115
           ${node_treeview_for_set_parent_menu(current_node.node_id, root_node_list)}
122
           ${node_treeview_for_set_parent_menu(current_node.node_id, root_node_list)}
116
         </ul>
123
         </ul>
117
 
124
 
118
-
119
-        <a href='${tg.url('/api/force_delete_node?node_id=%i'%(current_node.node_id))}' id='current-document-force-delete-button' class="btn" onclick="return confirm('${_('Delete current document?')}');"><i class="icon-g-remove"></i> ${_('Delete')}</a>
125
+        <a href='${tg.url('/api/edit_status?node_id=%i&node_status=%s'%(current_node.node_id, 'deleted'))}' id='current-document-force-delete-button' class="btn" onclick="return confirm('${_('Delete current document?')}');"><i class="fa fa-trash-o"></i> ${_('Delete')}</a>
126
+        
120
       </div>
127
       </div>
121
 
128
 
122
       <div class="row">
129
       <div class="row">
123
         <div id='application-document-panel' class="span5">
130
         <div id='application-document-panel' class="span5">
124
           <p>
131
           <p>
125
             <div id='current-document-content' class="">
132
             <div id='current-document-content' class="">
126
-              <h3 id="current-document-title">#${current_node.node_id} - ${current_node.data_label}</h3>
133
+              <h3 id="current-document-title">#${current_node.node_id} - ${current_node.data_label}
134
+                <span class="label ${current_node.getStatus().css}" href="#">${current_node.getStatus().label}</a>
135
+              
136
+              </h3>
127
               ${current_node.getContentWithTags()|n}
137
               ${current_node.getContentWithTags()|n}
128
             </div>
138
             </div>
129
             <form style='display: none;' id="current-document-content-edit-form" method='post' action='${tg.url('/api/edit_label_and_content')}'>
139
             <form style='display: none;' id="current-document-content-edit-form" method='post' action='${tg.url('/api/edit_label_and_content')}'>