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