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