retrieve all records when listing authority, and filter the user ones
[sface.git] / sface / screens / userscreen.py
1
2 import datetime
3 import os
4 import pickle
5 from PyQt4.QtCore import *
6 from PyQt4.QtGui import *
7
8 from sfa.util.record import SfaRecord, SliceRecord, AuthorityRecord, UserRecord
9 from sface.config import config
10 from sface.sfiprocess import SfiProcess
11 from sface.screens.sfascreen import SfaScreen
12
13 NAME_COLUMN = 0
14 #ROLE_COLUMN = 1
15 MEMBERSHIP_STATUS_COLUMN = 1
16 SERVER_MEMBERSHIP_STATUS_COLUMN = 2
17
18 # maximum length of a name to display before clipping
19 NAME_MAX_LEN = 48
20
21 user_status = { "in": "Already Selected",
22                 "out": "Not Selected",
23                 "add": "To be Added",
24                 "remove": "To be Removed"}
25
26 color_status = { "in": QColor.fromRgb(0, 250, 250),
27                  "add": QColor.fromRgb(0, 250, 0),
28                  "remove": QColor.fromRgb(250, 0, 0) }
29
30
31 class UserNameDelegate(QStyledItemDelegate):
32     def __init__(self, parent):
33         QStyledItemDelegate.__init__(self, parent)
34
35     def displayText(self, value, locale):
36         data = str(QStyledItemDelegate.displayText(self, value, locale))
37         if (len(data)>NAME_MAX_LEN):
38             data = data[:(NAME_MAX_LEN-3)] + "..."
39         return QString(data)
40
41     def paint(self, painter, option, index):
42         model = index.model()
43         data = str(self.displayText(index.data(), QLocale()))
44         status_index = model.index(index.row(), MEMBERSHIP_STATUS_COLUMN, index.parent())
45         status_data = status_index.data().toString()
46
47         fm = QFontMetrics(option.font)
48         rect = QRect(option.rect)
49
50         rect.setHeight(rect.height() - 2)
51         rect.setWidth(fm.width(QString(data)) + 6)
52         rect.setX(rect.x() + 5)
53         rect.setY(rect.y() - 1)
54
55         textRect = QRect(option.rect)
56         textRect.setWidth(fm.width(QString(data)) + 6)
57         textRect.setX(rect.x())
58
59         x, y, h, w = rect.x(), rect.y(), rect.height(), rect.width()
60
61         path = QPainterPath()
62         path.addRoundedRect(x - 1, y + 1, w, h, 4, 4)
63
64         painter.save()
65         painter.setRenderHint(QPainter.Antialiasing)
66
67         if option.state & QStyle.State_Selected:
68             painter.fillRect(option.rect, option.palette.color(QPalette.Active, QPalette.Highlight))
69
70         color = None
71         for x in user_status.keys():
72             if (user_status[x] == status_data) and (x in color_status):
73                 color = color_status[x]
74
75         if color != None:
76             painter.fillPath(path, color)
77         painter.setPen(QColor.fromRgb(0, 0, 0))
78         painter.drawText(textRect, Qt.AlignVCenter, QString(data))
79
80         painter.restore()
81
82 class UserView(QTableView):
83     def __init__(self, parent=None):
84         QTableView.__init__(self, parent)
85
86         self.setSelectionBehavior(QAbstractItemView.SelectRows)
87         self.setSelectionMode(QAbstractItemView.SingleSelection)
88         self.setAlternatingRowColors(True)
89         self.setAttribute(Qt.WA_MacShowFocusRect, 0)
90         self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
91         self.setToolTip("Double click on a row to change its status.")
92
93         self.setItemDelegateForColumn(0, UserNameDelegate(self))
94
95     def keyPressEvent(self, event):
96         if (event.key() == Qt.Key_Space):
97             self.toggleSelection()
98         else:
99             QTableView.keyPressEvent(self, event)
100
101     def mouseDoubleClickEvent(self, event):
102         self.toggleSelection()
103
104     def toggleSelection(self):
105         index = self.currentIndex()
106         model = index.model()
107         status_index = model.index(index.row(), MEMBERSHIP_STATUS_COLUMN, index.parent())
108         status_data = status_index.data().toString()
109         server_status_data = model.index(index.row(), SERVER_MEMBERSHIP_STATUS_COLUMN, index.parent()).data().toString()
110         node_index = model.index(index.row(), NAME_COLUMN, index.parent())
111         node_data = node_index.data().toString()
112
113         # This is a hostname
114         if status_data == user_status['in']:
115             model.setData(status_index, QString(user_status['remove']))
116         elif status_data == user_status['out']:
117             model.setData(status_index, QString(user_status['add']))
118         elif status_data in (user_status['add'], user_status['remove']):
119             if server_status_data == user_status["in"]:
120                 model.setData(status_index, QString(user_status['in']))
121             else:
122                 model.setData(status_index, QString(user_status['out']))
123
124         model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), node_index, node_index)
125
126     def currentChanged(self, current, previous):
127         model = current.model()
128         node_index = model.index(current.row(), 0, current.parent())
129         node_data = node_index.data().toString()
130
131     def hideUnusableColumns(self):
132         self.hideColumn(SERVER_MEMBERSHIP_STATUS_COLUMN)
133
134 class UserModel(QStandardItemModel):
135     def __init__(self, rows=0, columns=4, parent=None):
136          QStandardItemModel.__init__(self, rows, columns, parent)
137
138     def updateModel(self, sliceRec):
139         self.clear()
140
141         added_persons = []
142         slice_persons = []
143
144         if sliceRec:
145             #for pi in sliceRec.get_field("PI", default=[]):
146             #    name = str(pi)
147             #    if not name in added_persons:
148             #         slice_persons.append({"name": name, "role": "PI", "member": user_status["in"]})
149             #         added_persons.append(name)
150
151             for researcher in sliceRec.get_field("researcher", default=[]):
152                 name = str(researcher)
153                 if not name in added_persons:
154                      slice_persons.append({"name": name, "role": "researcher", "member": user_status["in"]})
155                      added_persons.append(name)
156
157         i=0
158         while (os.path.exists(config.getAuthorityListFile(i))):
159             rec = self.readUserRecord(i)
160             if rec:
161                 type = str(rec.get_type())
162                 if (type == "user"):
163                     name = str(rec.get_name())
164                     if not name in added_persons:
165                         slice_persons.append({"name": name, "role": "", "member": user_status["out"]})
166                         added_persons.append(name)
167             i=i+1
168
169         rootItem = self.invisibleRootItem()
170
171         for person in slice_persons:
172             rootItem.appendRow([self.readOnlyItem(person["name"]),
173                                 self.readOnlyItem(person["member"]),
174                                 self.readOnlyItem(person["member"])])
175
176         headers = QStringList() << "User Name" << "Status" << "ServerStatus"
177         self.setHorizontalHeaderLabels(headers)
178
179     def readOnlyItem(self, x):
180         item = QStandardItem(QString(x))
181         item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
182         return item
183
184     def updateRecord(self, slicerec):
185         change = False
186
187         item = self.invisibleRootItem()
188         children = item.rowCount()
189         for row in range(0, children):
190             childName = str(item.child(row, NAME_COLUMN).data(Qt.DisplayRole).toString())
191             childStatus = str(item.child(row, MEMBERSHIP_STATUS_COLUMN).data(Qt.DisplayRole).toString())
192
193             if (childStatus == user_status['add']):
194                 researcher = slicerec.get_field("researcher", [])
195                 researcher.append(childName)
196                 slicerec["researcher"] = researcher
197                 change = True
198             elif (childStatus == user_status['remove']):
199                 if childName in slicerec.get_field("PI"):
200                      slicerec.get_field("PI").remove(childName)
201                 if childName in slicerec.get_field("researcher"):
202                      slicerec.get_field("researcher").remove(childName)
203                 change = True
204
205         return change
206
207     def getResearchers(self):
208         researchers = []
209         item = self.invisibleRootItem()
210         children = item.rowCount()
211         for row in range(0, children):
212             childName = str(item.child(row, NAME_COLUMN).data(Qt.DisplayRole).toString())
213             childStatus = str(item.child(row, MEMBERSHIP_STATUS_COLUMN).data(Qt.DisplayRole).toString())
214
215             if (childStatus == user_status['add']) or (childStatus == user_status['in']):
216                 researchers.append(childName)
217
218         return researchers
219
220     def readUserRecord(self, i):
221         rec_file = config.getAuthorityListFile(i)
222         if os.path.exists(rec_file):
223             xml = open(rec_file).read()
224             rec = UserRecord()
225             rec.load_from_string(xml)
226             return rec
227         return None
228
229 class UsersWidget(QWidget):
230     def __init__(self, parent):
231         QWidget.__init__(self, parent)
232
233         self.process = SfiProcess(self)
234
235         self.slicename = QLabel("", self)
236         self.updateSliceName()
237         self.slicename.setScaledContents(False)
238         searchlabel = QLabel ("Search: ", self)
239         searchlabel.setScaledContents(False)
240         searchbox = QLineEdit(self)
241         searchbox.setAttribute(Qt.WA_MacShowFocusRect, 0)
242
243         toplayout = QHBoxLayout()
244         toplayout.addWidget(self.slicename, 0, Qt.AlignLeft)
245         toplayout.addStretch()
246         toplayout.addWidget(searchlabel, 0, Qt.AlignRight)
247         toplayout.addWidget(searchbox, 0, Qt.AlignRight)
248
249         self.userView = UserView()
250
251         refresh = QPushButton("Refresh", self)
252         refresh.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
253         submit = QPushButton("Submit", self)
254         submit.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
255
256         bottomlayout = QHBoxLayout()
257         bottomlayout.addWidget(refresh, 0, Qt.AlignLeft)
258         bottomlayout.addStretch()
259         bottomlayout.addWidget(submit, 0, Qt.AlignRight)
260
261         layout = QVBoxLayout()
262         layout.addLayout(toplayout)
263         layout.addWidget(self.userView)
264         layout.addLayout(bottomlayout)
265         self.setLayout(layout)
266         self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
267
268         self.userModel = UserModel(parent=self)
269
270         self.connect(refresh, SIGNAL('clicked()'), self.refresh)
271         self.connect(submit, SIGNAL('clicked()'), self.submit)
272
273         self.updateView()
274
275     def submitFinished(self):
276         self.disconnect(self.process, SIGNAL('finished()'), self.submitFinished)
277
278         faultString = self.process.getFaultString()
279         if not faultString:
280             self.setStatus("<font color='green'>Slice user data submitted.</font>")
281             QTimer.singleShot(1000, self.refresh)
282         else:
283             self.setStatus("<font color='red'>Slice user submit failed: %s</font>" % (faultString))
284
285     def getSliceRecordFinished(self):
286         self.disconnect(self.process, SIGNAL('finished()'), self.getSliceRecordFinished)
287
288         faultString = self.process.getFaultString()
289         if not faultString:
290             self.setStatus("<font color='green'>Slice record refreshed.</font>")
291             self.refreshAuthority()
292         else:
293             self.setStatus("<font color='red'>Slice rec refresh error: %s</font>" % (faultString))
294
295     def getAuthorityRecordFinished(self):
296         self.disconnect(self.process, SIGNAL('finished()'), self.getAuthorityRecordFinished)
297
298         faultString = self.process.getFaultString()
299         if not faultString:
300             self.setStatus("<font color='green'>User data refreshed.</font>")
301             self.updateView()
302             #self.parent().signalAll("usersUpdated")
303         else:
304             self.setStatus("<font color='red'>Authority rec refresh error: %s</font>" % (faultString))
305
306     def readSliceRecord(self):
307         rec_file = config.getSliceRecordFile()
308         if os.path.exists(rec_file):
309             xml = open(rec_file).read()
310             rec = SliceRecord()
311             rec.load_from_string(xml)
312             return rec
313         return None
314
315     def readAuthorityRecord(self):
316         rec_file = config.getAuthorityRecordFile()
317         if os.path.exists(rec_file):
318             xml = open(rec_file).read()
319             rec = AuthorityRecord()
320             rec.load_from_string(xml)
321             return rec
322         return None
323
324     def setStatus(self, msg, timeout=None):
325         self.parent().setStatus(msg, timeout)
326
327     def checkRunningProcess(self):
328         if self.process.isRunning():
329             self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
330             return True
331         return False
332
333     def submit(self):
334         if self.checkRunningProcess():
335             return
336
337         rec = self.readSliceRecord()
338         change = self.userModel.updateRecord(rec)
339
340         if not change:
341             self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
342             return
343
344         rec_file = config.getSliceRecordFile()
345         file(rec_file, "w").write(rec.save_to_string())
346
347         self.connect(self.process, SIGNAL('finished()'), self.submitFinished)
348
349         self.process.updateRecord(rec_file)
350         self.setStatus("Sending slice record. This will take some time...")
351
352     def refresh(self):
353         if not config.getSlice():
354             self.setStatus("<font color='red'>Slice not set yet!</font>")
355             return
356
357         if self.process.isRunning():
358             self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
359             return
360
361         self.connect(self.process, SIGNAL('finished()'), self.getSliceRecordFinished)
362
363         self.process.getSliceRecord()
364         self.setStatus("Refreshing slice record. This will take some time...")
365
366     def refreshAuthority(self):
367         self.connect(self.process, SIGNAL('finished()'), self.getAuthorityRecordFinished)
368
369         self.process.listRecords(config.getAuthority(), None, config.getAuthorityListFile())
370         self.setStatus("Refreshing user records. This will take some time...")
371
372     def updateView(self):
373         sliceRec = self.readSliceRecord()
374
375         if not sliceRec:
376             # wait until we've resolved the slicerecord before displaying
377             # anything to the user.
378             self.userModel.clear()
379         else:
380             self.userModel.updateModel(sliceRec)
381
382         self.userView.setModel(self.userModel)
383         self.userView.hideColumn(SERVER_MEMBERSHIP_STATUS_COLUMN)
384         self.userView.resizeColumnToContents(0)
385
386     def updateSliceName(self):
387         self.slicename.setText("Slice : %s" % (config.getSlice() or "None"))
388
389
390
391
392 class UserScreen(SfaScreen):
393     def __init__(self, parent):
394         SfaScreen.__init__(self, parent)
395
396         slice = UsersWidget(self)
397         self.init(slice, "Users", "OneLab SFA crawler")
398
399     def configurationChanged(self):
400         self.widget.updateSliceName()
401         self.widget.updateView()
402
403