support spacebar for user selection; make user names readonly
[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 user_status = { "in": "Already Selected",
19                 "out": "Not Selected",
20                 "add": "To be Added",
21                 "remove": "To be Removed"}
22
23 class UserView(QTableView):
24     def __init__(self, parent=None):
25         QTableView.__init__(self, parent)
26
27         self.setSelectionBehavior(QAbstractItemView.SelectRows)
28         self.setSelectionMode(QAbstractItemView.SingleSelection)
29         self.setAlternatingRowColors(True)
30         self.setAttribute(Qt.WA_MacShowFocusRect, 0)
31         self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
32         self.setToolTip("Double click on a row to change its status.")
33
34     def keyPressEvent(self, event):
35         if (event.key() == Qt.Key_Space):
36             self.toggleSelection()
37         else:
38             QTableView.keyPressEvent(self, event)
39
40     def mouseDoubleClickEvent(self, event):
41         self.toggleSelection()
42
43     def toggleSelection(self):
44         index = self.currentIndex()
45         model = index.model()
46         status_index = model.index(index.row(), MEMBERSHIP_STATUS_COLUMN, index.parent())
47         status_data = status_index.data().toString()
48         server_status_data = model.index(index.row(), SERVER_MEMBERSHIP_STATUS_COLUMN, index.parent()).data().toString()
49         node_index = model.index(index.row(), NAME_COLUMN, index.parent())
50         node_data = node_index.data().toString()
51
52         # This is a hostname
53         if status_data == user_status['in']:
54             model.setData(status_index, QString(user_status['remove']))
55         elif status_data == user_status['out']:
56             model.setData(status_index, QString(user_status['add']))
57         elif status_data in (user_status['add'], user_status['remove']):
58             if server_status_data == user_status["in"]:
59                 model.setData(status_index, QString(user_status['in']))
60             else:
61                 model.setData(status_index, QString(user_status['out']))
62
63         model.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), node_index, node_index)
64
65     def currentChanged(self, current, previous):
66         model = current.model()
67         node_index = model.index(current.row(), 0, current.parent())
68         node_data = node_index.data().toString()
69         self.emit(SIGNAL('hostnameClicked(QString)'), node_data)
70
71     def hideUnusableColumns(self):
72         self.hideColumn(SERVER_MEMBERSHIP_STATUS_COLUMN)
73
74 class UserModel(QStandardItemModel):
75     def __init__(self, rows=0, columns=4, parent=None):
76          QStandardItemModel.__init__(self, rows, columns, parent)
77
78     def updateModel(self, sliceRec):
79         self.clear()
80
81         added_persons = []
82         slice_persons = []
83
84         if sliceRec:
85             #for pi in sliceRec.get_field("PI", default=[]):
86             #    name = str(pi)
87             #    if not name in added_persons:
88             #         slice_persons.append({"name": name, "role": "PI", "member": user_status["in"]})
89             #         added_persons.append(name)
90
91             for researcher in sliceRec.get_field("researcher", default=[]):
92                 name = str(researcher)
93                 if not name in added_persons:
94                      slice_persons.append({"name": name, "role": "researcher", "member": user_status["in"]})
95                      added_persons.append(name)
96
97         i=0
98         while (os.path.exists(config.getAuthorityListFile(i))):
99             rec = self.readUserRecord(i)
100             if rec:
101                 name = str(rec.get_name())
102                 if not name in added_persons:
103                     slice_persons.append({"name": name, "role": "", "member": user_status["out"]})
104                     added_persons.append(name)
105             i=i+1
106
107         rootItem = self.invisibleRootItem()
108
109         for person in slice_persons:
110             rootItem.appendRow([self.readOnlyItem(person["name"]),
111                                 self.readOnlyItem(person["member"]),
112                                 self.readOnlyItem(person["member"])])
113
114         headers = QStringList() << "User Name" << "Status" << "ServerStatus"
115         self.setHorizontalHeaderLabels(headers)
116
117     def readOnlyItem(self, x):
118         item = QStandardItem(QString(x))
119         item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
120         return item
121
122     def updateRecord(self, slicerec):
123         change = False
124
125         item = self.invisibleRootItem()
126         children = item.rowCount()
127         for row in range(0, children):
128             childName = str(item.child(row, NAME_COLUMN).data(Qt.DisplayRole).toString())
129             childStatus = str(item.child(row, MEMBERSHIP_STATUS_COLUMN).data(Qt.DisplayRole).toString())
130
131             if (childStatus == user_status['add']):
132                 researcher = slicerec.get_field("researcher", [])
133                 researcher.append(childName)
134                 slicerec["researcher"] = researcher
135                 change = True
136             elif (childStatus == user_status['remove']):
137                 if childName in slicerec.get_field("PI"):
138                      slicerec.get_field("PI").remove(childName)
139                 if childName in slicerec.get_field("researcher"):
140                      slicerec.get_field("researcher").remove(childName)
141                 change = True
142
143         return change
144
145     def getResearchers(self):
146         researchers = []
147         item = self.invisibleRootItem()
148         children = item.rowCount()
149         for row in range(0, children):
150             childName = str(item.child(row, NAME_COLUMN).data(Qt.DisplayRole).toString())
151             childStatus = str(item.child(row, MEMBERSHIP_STATUS_COLUMN).data(Qt.DisplayRole).toString())
152
153             if (childStatus == user_status['add']) or (childStatus == user_status['in']):
154                 researchers.append(childName)
155
156         return researchers
157
158     def readUserRecord(self, i):
159         rec_file = config.getAuthorityListFile(i)
160         if os.path.exists(rec_file):
161             xml = open(rec_file).read()
162             rec = UserRecord()
163             rec.load_from_string(xml)
164             return rec
165         return None
166
167 class UsersWidget(QWidget):
168     def __init__(self, parent):
169         QWidget.__init__(self, parent)
170
171         self.process = SfiProcess(self)
172
173         self.slicename = QLabel("", self)
174         self.updateSliceName()
175         self.slicename.setScaledContents(False)
176         searchlabel = QLabel ("Search: ", self)
177         searchlabel.setScaledContents(False)
178         searchbox = QLineEdit(self)
179         searchbox.setAttribute(Qt.WA_MacShowFocusRect, 0)
180
181         toplayout = QHBoxLayout()
182         toplayout.addWidget(self.slicename, 0, Qt.AlignLeft)
183         toplayout.addStretch()
184         toplayout.addWidget(searchlabel, 0, Qt.AlignRight)
185         toplayout.addWidget(searchbox, 0, Qt.AlignRight)
186
187         self.userView = UserView()
188
189         refresh = QPushButton("Refresh", self)
190         refresh.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
191         submit = QPushButton("Submit", self)
192         submit.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
193
194         bottomlayout = QHBoxLayout()
195         bottomlayout.addWidget(refresh, 0, Qt.AlignLeft)
196         bottomlayout.addStretch()
197         bottomlayout.addWidget(submit, 0, Qt.AlignRight)
198
199         layout = QVBoxLayout()
200         layout.addLayout(toplayout)
201         layout.addWidget(self.userView)
202         layout.addLayout(bottomlayout)
203         self.setLayout(layout)
204         self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
205
206         self.userModel = UserModel(parent=self)
207
208         self.connect(refresh, SIGNAL('clicked()'), self.refresh)
209         self.connect(submit, SIGNAL('clicked()'), self.submit)
210
211         self.updateView()
212
213     def submitFinished(self):
214         self.setStatus("<font color='green'>Slice data submitted.</font>")
215         QTimer.singleShot(1000, self.refresh)
216
217     def getSliceRecordFinished(self):
218         self.setStatus("<font color='green'>Authority data refreshed.</font>", timeout=5000)
219         self.refreshAuthority()
220
221     def getAuthorityRecordFinished(self):
222         self.setStatus("<font color='green'>Slice data refreshed.</font>", timeout=5000)
223         self.updateView()
224         #self.parent().signalAll("usersUpdated")
225
226     def readSliceRecord(self):
227         rec_file = config.getSliceRecordFile()
228         if os.path.exists(rec_file):
229             xml = open(rec_file).read()
230             rec = SliceRecord()
231             rec.load_from_string(xml)
232             return rec
233         return None
234
235     def readAuthorityRecord(self):
236         rec_file = config.getAuthorityRecordFile()
237         if os.path.exists(rec_file):
238             xml = open(rec_file).read()
239             rec = AuthorityRecord()
240             rec.load_from_string(xml)
241             return rec
242         return None
243
244     def setStatus(self, msg, timeout=None):
245         self.parent().setStatus(msg, timeout)
246
247     def checkRunningProcess(self):
248         if self.process.isRunning():
249             self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
250             return True
251         return False
252
253     def submit(self):
254         if self.checkRunningProcess():
255             return
256
257         rec = self.readSliceRecord()
258         change = self.userModel.updateRecord(rec)
259
260         if not change:
261             self.setStatus("<font color=red>No change in slice data. Not submitting!</font>", timeout=3000)
262             return
263
264         rec_file = config.getSliceRecordFile()
265         file(rec_file, "w").write(rec.save_to_string())
266
267         self.disconnect(self.process, SIGNAL('finished()'), self.getAuthorityRecordFinished)
268         self.connect(self.process, SIGNAL('finished()'), self.submitFinished)
269
270         self.process.updateRecord(rec_file)
271         self.setStatus("Sending slice record. This will take some time...")
272
273     def refresh(self):
274         if not config.getSlice():
275             self.setStatus("<font color='red'>Slice not set yet!</font>")
276             return
277
278         if self.process.isRunning():
279             self.setStatus("<font color='red'>There is already a process running. Please wait.</font>")
280             return
281
282         self.disconnect(self.process, SIGNAL('finished()'), self.submitFinished)
283         self.connect(self.process, SIGNAL('finished()'), self.getSliceRecordFinished)
284
285         self.process.getSliceRecord()
286         self.setStatus("Refreshing slice record. This will take some time...")
287
288     def refreshAuthority(self):
289         self.disconnect(self.process, SIGNAL('finished()'), self.getSliceRecordFinished)
290         self.connect(self.process, SIGNAL('finished()'), self.getAuthorityRecordFinished)
291
292         self.process.listRecords(config.getAuthority(), "user", config.getAuthorityListFile())
293         self.setStatus("Refreshing user records. This will take some time...")
294
295     def updateView(self):
296         sliceRec = self.readSliceRecord()
297
298         if not sliceRec:
299             # wait until we've resolved the slicerecord before displaying
300             # anything to the user.
301             self.userModel.clear()
302         else:
303             self.userModel.updateModel(sliceRec)
304
305         self.userView.setModel(self.userModel)
306         self.userView.hideColumn(SERVER_MEMBERSHIP_STATUS_COLUMN)
307         self.userView.resizeColumnToContents(0)
308
309     def updateSliceName(self):
310         self.slicename.setText("Slice : %s" % (config.getSlice() or "None"))
311
312
313
314
315 class UserScreen(SfaScreen):
316     def __init__(self, parent):
317         SfaScreen.__init__(self, parent)
318
319         slice = UsersWidget(self)
320         self.init(slice, "Users", "OneLab SFA crawler")
321
322     def configurationChanged(self):
323         self.widget.updateSliceName()
324         self.widget.updateView()
325
326