1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 """Tableview"""
29
30 import os
31 import logging
32 logger = logging.getLogger('camelot.view.controls.tableview')
33
34 from PyQt4 import QtCore, QtGui
35 from PyQt4.QtGui import QSizePolicy
36 from PyQt4.QtCore import SIGNAL
37
38 from camelot.view.proxy.queryproxy import QueryTableProxy
39 import settings
40
41 verbose = False
42
43
45 """the actual displayed table"""
46
48 QtGui.QTableView.__init__(self, parent)
49 logger.debug('create querytable')
50 self.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
51 self.setEditTriggers(QtGui.QAbstractItemView.AllEditTriggers)
52 self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
53 self.horizontalHeader().setClickable(False)
54
55
57 """emits the row_selected signal when a row has been selected"""
58
59 - def __init__(self, admin, search_text=None, parent=None):
60 from search import SimpleSearchControl
61 from inheritance import SubclassTree
62 QtGui.QWidget.__init__(self, parent)
63 self.setWindowTitle(admin.getName())
64 self.widget_layout = QtGui.QHBoxLayout()
65 self.widget_layout.setSpacing(0)
66 self.widget_layout.setMargin(0)
67 self.table_layout = QtGui.QVBoxLayout()
68 self.table_layout.setSpacing(0)
69 self.table_layout.setMargin(0)
70 self.search_control = SimpleSearchControl()
71 self.table = None
72 self.filters = None
73 self.admin = admin
74 self.table_model = None
75 self.table_layout.insertWidget(0, self.search_control)
76 self.setSubclass(admin)
77 self.class_tree = SubclassTree(admin, self)
78 self.widget_layout.insertWidget(0, self.class_tree)
79 self.widget_layout.insertLayout(1, self.table_layout)
80 self.closeAfterValidation = QtCore.SIGNAL('closeAfterValidation()')
81 self.connect(self.search_control, SIGNAL('search'), self.startSearch)
82 self.connect(self.search_control, SIGNAL('cancel'), self.cancelSearch)
83 self.connect(self.class_tree, SIGNAL('subclasssClicked'), self.setSubclass)
84 self.search_filter = lambda q: q
85 self.setLayout(self.widget_layout)
86 self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
87 if search_text:
88 self.search_control.search(search_text)
89
91 """emits a row_selected signal"""
92 self.emit(SIGNAL('row_selected'), section)
93
95 """Switch to a different subclass, where admin is the admin object of the
96 subclass"""
97 self.admin = admin
98 if self.table:
99 self.table.deleteLater()
100 self.table_model.deleteLater()
101 self.table = QueryTable()
102
103
104 self.table_model = QueryTableProxy(admin,
105 lambda:admin.entity.query.limit(10),
106 admin.getColumns)
107 self.table.setModel(self.table_model)
108 self.connect(self.table.verticalHeader(),
109 SIGNAL('sectionClicked(int)'),
110 self.sectionClicked)
111 self.table_layout.insertWidget(1, self.table)
112
113 def update_delegates(*args):
114 """update item delegate"""
115 self.table.setItemDelegate(self.table_model.getItemDelegate())
116
117 admin.mt.post(lambda: None, update_delegates)
118
119 admin.mt.post(lambda: self.table_model._extend_cache(0, 10),
120 lambda x: self.resizeColumnsAndRebuildQuery())
121 admin.mt.post(lambda: admin.getFilters(),
122 lambda items: self.setFilters(items))
123 admin.mt.post(lambda: admin.getListCharts(),
124 lambda charts: self.setCharts(charts))
125
127 """creates and display charts"""
128 if charts:
129
130 from matplotlib.figure import Figure
131 from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as \
132 FigureCanvas
133
134 chart = charts[0]
135
136 def getData():
137 """fetches data for chart"""
138 from sqlalchemy.sql import select, func
139 from elixir import session
140 xcol = getattr(self.admin.entity, chart['x'])
141 ycol = getattr(self.admin.entity, chart['y'])
142 session.bind = self.admin.entity.table.metadata.bind
143 result = session.execute(select([xcol, func.sum(ycol)]).group_by(xcol))
144 summary = result.fetchall()
145 return [s[0] for s in summary], [s[1] for s in summary]
146
147 class MyMplCanvas(FigureCanvas):
148 """Ultimately, this is a QWidget (as well as a FigureCanvasAgg)"""
149
150 def __init__(self, parent=None, width=5, height=4, dpi=100):
151 fig = Figure(figsize=(width, height), dpi=dpi, facecolor='w')
152 self.axes = fig.add_subplot(111, axisbg='w')
153
154 self.axes.hold(False)
155 self.compute_initial_figure()
156 FigureCanvas.__init__(self, fig)
157 self.setParent(parent)
158 FigureCanvas.setSizePolicy(self,
159 QSizePolicy.Expanding,
160 QSizePolicy.Expanding)
161 FigureCanvas.updateGeometry(self)
162
163
164 def compute_initial_figure(self):
165 pass
166
167 def setData(data):
168 """set chart data"""
169
170 class MyStaticMplCanvas(MyMplCanvas):
171 """simple canvas with a sine plot"""
172
173 def compute_initial_figure(self):
174 """computes initial figure"""
175 x, y = data
176 bar_positions = [i-0.25 for i in range(1, len(x)+1)]
177 width = 0.5
178 self.axes.bar(bar_positions, y, width, color='b')
179 self.axes.set_xlabel('Year')
180 self.axes.set_ylabel('Sales')
181 self.axes.set_xticks(range(len(x)+1))
182 self.axes.set_xticklabels(['']+[str(d) for d in x])
183
184 sc = MyStaticMplCanvas(self, width=5, height=4, dpi=100)
185 self.table_layout.addWidget(sc)
186
187 self.admin.mt.post(getData, setData)
188
190 """resizes table of columns"""
191 logger.debug('resizeColumnsAndRebuildQuery')
192
193
194 if self.table_model.rowCount() > 1:
195 self.table.resizeColumnsToContents()
196 self.rebuildQuery()
197
198
199
200
201
202
204 """delete the selected rows in this tableview"""
205 logger.debug('delete selected rows called')
206 for row in set(map(lambda x: x.row(), self.table.selectedIndexes())):
207 self.table_model.removeRow(row)
208
218
220 """selects the specified row"""
221 self.table.selectRow(row)
222
224 """returns a list of selected rows indexes"""
225 return self.table.selectedIndexes()
226
228 """return the columns to be displayed in the table view"""
229 return self.admin.getColumns()
230
232 """generator for data queried by table model"""
233 for d in self.table_model.getData():
234 yield d
235
237 """return the name of the entity managed by the admin attribute"""
238 return self.admin.getName()
239
243
247
253
259
261 """resets the table model query"""
262
263 def rebuild_query():
264 query = self.admin.entity.query
265 if self.filters:
266 query = self.filters.decorate_query(query)
267 if self.search_filter:
268 query = self.search_filter(query)
269 self.table_model.setQuery(lambda:query)
270
271 self.admin.mt.post(rebuild_query)
272
279
281 """resets search filtering to default"""
282 logger.debug('cancel search')
283 self.search_filter = lambda q: q
284 self.rebuildQuery()
285
300
302 """generates html of the table"""
303 table = [[getattr(row, col[0]) for col in self.admin.getColumns()]
304 for row in self.admin.entity.query.all()]
305 context = {
306 'title': self.admin.getName(),
307 'table': table,
308 'columns': [c[0] for c in self.admin.getColumns()],
309 }
310 from jinja import Environment, FileSystemLoader
311 ld = FileSystemLoader(settings.CAMELOT_TEMPLATES_DIRECTORY)
312 env = Environment(loader=ld)
313 tp = env.get_template('table_view.html')
314 return tp.render(context)
315
317 """reimplements close event"""
318 logger.debug('tableview closed')
319
320 del self.widget_layout
321 del self.table_layout
322 del self.search_control
323 del self.table
324 del self.filters
325 del self.class_tree
326 del self.table_model
327 event.accept()
328
330 """deletes the tableview object"""
331 logger.debug('%s deleted' % self.__class__.__name__)
332