| 1 | # ============================================================================
|
|---|
| 2 | #
|
|---|
| 3 | # Copyright (C) 2007-2008 Conceptive Engineering bvba. All rights reserved.
|
|---|
| 4 | # www.conceptive.be / project-camelot@conceptive.be
|
|---|
| 5 | #
|
|---|
| 6 | # This file is part of the Camelot Library.
|
|---|
| 7 | #
|
|---|
| 8 | # This file may be used under the terms of the GNU General Public
|
|---|
| 9 | # License version 2.0 as published by the Free Software Foundation
|
|---|
| 10 | # and appearing in the file LICENSE.GPL included in the packaging of
|
|---|
| 11 | # this file. Please review the following information to ensure GNU
|
|---|
| 12 | # General Public Licensing requirements will be met:
|
|---|
| 13 | # http://www.trolltech.com/products/qt/opensource.html
|
|---|
| 14 | #
|
|---|
| 15 | # If you are unsure which license is appropriate for your use, please
|
|---|
| 16 | # review the following information:
|
|---|
| 17 | # http://www.trolltech.com/products/qt/licensing.html or contact
|
|---|
| 18 | # project-camelot@conceptive.be.
|
|---|
| 19 | #
|
|---|
| 20 | # This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
|
|---|
| 21 | # WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
|
|---|
| 22 | #
|
|---|
| 23 | # For use of this library in commercial applications, please contact
|
|---|
| 24 | # project-camelot@conceptive.be
|
|---|
| 25 | #
|
|---|
| 26 | # ============================================================================
|
|---|
| 27 |
|
|---|
| 28 | """Tableview"""
|
|---|
| 29 |
|
|---|
| 30 | import logging
|
|---|
| 31 | logger = logging.getLogger( 'camelot.view.controls.tableview' )
|
|---|
| 32 |
|
|---|
| 33 | from PyQt4 import QtCore, QtGui
|
|---|
| 34 | from PyQt4.QtGui import QSizePolicy
|
|---|
| 35 | from PyQt4.QtCore import SIGNAL
|
|---|
| 36 | from PyQt4.QtCore import Qt
|
|---|
| 37 |
|
|---|
| 38 | from camelot.view.proxy.queryproxy import QueryTableProxy
|
|---|
| 39 | from camelot.view.controls.view import AbstractView
|
|---|
| 40 | from camelot.view.controls.user_translatable_label import UserTranslatableLabel
|
|---|
| 41 | from camelot.view.model_thread import model_function, gui_function, post
|
|---|
| 42 | from camelot.core.utils import ugettext as _
|
|---|
| 43 |
|
|---|
| 44 | from search import SimpleSearchControl
|
|---|
| 45 |
|
|---|
| 46 | class TableWidget( QtGui.QTableView):
|
|---|
| 47 | """A widget displaying a table, to be used within a TableView"""
|
|---|
| 48 |
|
|---|
| 49 | def __init__( self, parent = None ):
|
|---|
| 50 | QtGui.QTableView.__init__( self, parent )
|
|---|
| 51 | logger.debug( 'create TableWidget' )
|
|---|
| 52 | self.setSelectionBehavior( QtGui.QAbstractItemView.SelectRows )
|
|---|
| 53 | self.setEditTriggers( QtGui.QAbstractItemView.SelectedClicked | QtGui.QAbstractItemView.DoubleClicked )
|
|---|
| 54 | self.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Expanding )
|
|---|
| 55 | # set to false while sorting is not implemented in CollectionProxy
|
|---|
| 56 | self.horizontalHeader().setClickable( True )
|
|---|
| 57 | self._header_font_required = QtGui.QApplication.font()
|
|---|
| 58 | self._header_font_required.setBold( True )
|
|---|
| 59 | self._minimal_row_height = QtGui.QFontMetrics(QtGui.QApplication.font()).lineSpacing() + 10
|
|---|
| 60 | self.verticalHeader().setDefaultSectionSize( self._minimal_row_height )
|
|---|
| 61 | self.connect( self.horizontalHeader(), QtCore.SIGNAL('sectionClicked(int)'), self.horizontal_section_clicked )
|
|---|
| 62 |
|
|---|
| 63 | def horizontal_section_clicked( self, logical_index ):
|
|---|
| 64 | """Update the sorting of the model and the header"""
|
|---|
| 65 | header = self.horizontalHeader()
|
|---|
| 66 | order = Qt.AscendingOrder
|
|---|
| 67 | if not header.isSortIndicatorShown():
|
|---|
| 68 | header.setSortIndicatorShown( True )
|
|---|
| 69 | elif header.sortIndicatorSection()==logical_index:
|
|---|
| 70 | # apparently, the sort order on the header is allready switched when the section
|
|---|
| 71 | # was clicked, so there is no need to reverse it
|
|---|
| 72 | order = header.sortIndicatorOrder()
|
|---|
| 73 | header.setSortIndicator( logical_index, order )
|
|---|
| 74 | self.model().sort( logical_index, order )
|
|---|
| 75 |
|
|---|
| 76 | def setModel( self, model ):
|
|---|
| 77 | QtGui.QTableView.setModel( self, model )
|
|---|
| 78 | self.connect( self.selectionModel(), SIGNAL( 'currentChanged(const QModelIndex&,const QModelIndex&)' ), self.activated )
|
|---|
| 79 |
|
|---|
| 80 | def activated( self, selectedIndex, previousSelectedIndex ):
|
|---|
| 81 | option = QtGui.QStyleOptionViewItem()
|
|---|
| 82 | newSize = self.itemDelegate( selectedIndex ).sizeHint( option, selectedIndex )
|
|---|
| 83 | row = selectedIndex.row()
|
|---|
| 84 | if previousSelectedIndex.row() >= 0:
|
|---|
| 85 | oldSize = self.itemDelegate( previousSelectedIndex ).sizeHint( option, selectedIndex )
|
|---|
| 86 | previousRow = previousSelectedIndex.row()
|
|---|
| 87 | self.setRowHeight( previousRow, oldSize.height() )
|
|---|
| 88 | self.setRowHeight( row, newSize.height() )
|
|---|
| 89 |
|
|---|
| 90 | class RowsWidget( QtGui.QLabel ):
|
|---|
| 91 | """Widget that is part of the header widget, displaying the number of rows
|
|---|
| 92 | in the table view"""
|
|---|
| 93 |
|
|---|
| 94 | _number_of_rows_font = QtGui.QApplication.font()
|
|---|
| 95 |
|
|---|
| 96 | def __init__( self, parent ):
|
|---|
| 97 | QtGui.QLabel.__init__( self, parent )
|
|---|
| 98 | self.setFont( self._number_of_rows_font )
|
|---|
| 99 |
|
|---|
| 100 | def setNumberOfRows( self, rows ):
|
|---|
| 101 | self.setText( _('(%i rows)')%rows )
|
|---|
| 102 |
|
|---|
| 103 | class HeaderWidget( QtGui.QWidget ):
|
|---|
| 104 | """HeaderWidget for a tableview, containing the title, the search widget,
|
|---|
| 105 | and the number of rows in the table"""
|
|---|
| 106 |
|
|---|
| 107 | search_widget = SimpleSearchControl
|
|---|
| 108 | rows_widget = RowsWidget
|
|---|
| 109 |
|
|---|
| 110 | _title_font = QtGui.QApplication.font()
|
|---|
| 111 | _title_font.setBold( True )
|
|---|
| 112 |
|
|---|
| 113 | def __init__( self, parent, admin ):
|
|---|
| 114 | QtGui.QWidget.__init__( self, parent )
|
|---|
| 115 | widget_layout = QtGui.QHBoxLayout()
|
|---|
| 116 | self.search = self.search_widget( self )
|
|---|
| 117 | title = UserTranslatableLabel( admin.get_verbose_name_plural(), self )
|
|---|
| 118 | title.setFont( self._title_font )
|
|---|
| 119 | widget_layout.addWidget( title )
|
|---|
| 120 | widget_layout.addWidget( self.search )
|
|---|
| 121 | if self.rows_widget:
|
|---|
| 122 | self.number_of_rows = self.rows_widget( self )
|
|---|
| 123 | widget_layout.addWidget( self.number_of_rows )
|
|---|
| 124 |
|
|---|
| 125 | else:
|
|---|
| 126 | self.number_of_rows = None
|
|---|
| 127 | self.setLayout( widget_layout )
|
|---|
| 128 | self.setSizePolicy( QSizePolicy.Minimum, QSizePolicy.Fixed )
|
|---|
| 129 | self.setNumberOfRows( 0 )
|
|---|
| 130 |
|
|---|
| 131 | @gui_function
|
|---|
| 132 | def setNumberOfRows( self, rows ):
|
|---|
| 133 | if self.number_of_rows:
|
|---|
| 134 | self.number_of_rows.setNumberOfRows( rows )
|
|---|
| 135 |
|
|---|
| 136 | class TableView( AbstractView ):
|
|---|
| 137 | """A generic tableview widget that puts together some other widgets. The behaviour of this class and
|
|---|
| 138 | the resulting interface can be tuned by specifying specific class attributes which define the underlying
|
|---|
| 139 | widgets used ::
|
|---|
| 140 |
|
|---|
| 141 | class MovieRentalTableView(TableView):
|
|---|
| 142 | title_format = 'Grand overview of recent movie rentals'
|
|---|
| 143 |
|
|---|
| 144 | The attributes that can be specified are :
|
|---|
| 145 |
|
|---|
| 146 | .. attribute:: header_widget
|
|---|
| 147 |
|
|---|
| 148 | The widget class to be used as a header in the table view::
|
|---|
| 149 |
|
|---|
| 150 | header_widget = HeaderWidget
|
|---|
| 151 |
|
|---|
| 152 | .. attribute:: table_widget
|
|---|
| 153 |
|
|---|
| 154 | The widget class used to display a table within the table view ::
|
|---|
| 155 |
|
|---|
| 156 | table_widget = TableWidget
|
|---|
| 157 |
|
|---|
| 158 | .. attribute:: title_format
|
|---|
| 159 |
|
|---|
| 160 | A string used to format the title of the view ::
|
|---|
| 161 |
|
|---|
| 162 | title_format = '%(verbose_name_plural)s'
|
|---|
| 163 |
|
|---|
| 164 | .. attribute:: table_model
|
|---|
| 165 |
|
|---|
| 166 | A class implementing QAbstractTableModel that will be used as a model for the table view ::
|
|---|
| 167 |
|
|---|
| 168 | table_model = QueryTableProxy
|
|---|
| 169 |
|
|---|
| 170 | - emits the row_selected signal when a row has been selected
|
|---|
| 171 | """
|
|---|
| 172 |
|
|---|
| 173 | header_widget = HeaderWidget
|
|---|
| 174 | table_widget = TableWidget
|
|---|
| 175 |
|
|---|
| 176 | #
|
|---|
| 177 | # The proxy class to use
|
|---|
| 178 | #
|
|---|
| 179 | table_model = QueryTableProxy
|
|---|
| 180 | #
|
|---|
| 181 | # Format to use as the window title
|
|---|
| 182 | #
|
|---|
| 183 | title_format = '%(verbose_name_plural)s'
|
|---|
| 184 |
|
|---|
| 185 | def __init__( self, admin, search_text = None, parent = None ):
|
|---|
| 186 | AbstractView.__init__( self, parent )
|
|---|
| 187 | self.admin = admin
|
|---|
| 188 | post( self.get_title, self.change_title )
|
|---|
| 189 | widget_layout = QtGui.QVBoxLayout()
|
|---|
| 190 | if self.header_widget:
|
|---|
| 191 | self.header = self.header_widget( self, admin )
|
|---|
| 192 | widget_layout.addWidget( self.header )
|
|---|
| 193 | self.connect( self.header.search, SIGNAL( 'search' ), self.startSearch )
|
|---|
| 194 | self.connect( self.header.search, SIGNAL( 'cancel' ), self.cancelSearch )
|
|---|
| 195 | if search_text:
|
|---|
| 196 | self.header.search.search( search_text )
|
|---|
| 197 | else:
|
|---|
| 198 | self.header = None
|
|---|
| 199 | widget_layout.setSpacing( 0 )
|
|---|
| 200 | widget_layout.setMargin( 0 )
|
|---|
| 201 | self.splitter = QtGui.QSplitter( self )
|
|---|
| 202 | widget_layout.addWidget( self.splitter )
|
|---|
| 203 | table_widget = QtGui.QWidget( self )
|
|---|
| 204 | filters_widget = QtGui.QWidget( self )
|
|---|
| 205 | self.table_layout = QtGui.QVBoxLayout()
|
|---|
| 206 | self.table_layout.setSpacing( 0 )
|
|---|
| 207 | self.table_layout.setMargin( 0 )
|
|---|
| 208 | self.table = None
|
|---|
| 209 | self.filters_layout = QtGui.QVBoxLayout()
|
|---|
| 210 | self.filters_layout.setSpacing( 0 )
|
|---|
| 211 | self.filters_layout.setMargin( 0 )
|
|---|
| 212 | self.filters = None
|
|---|
| 213 | self.actions = None
|
|---|
| 214 | self._table_model = None
|
|---|
| 215 | table_widget.setLayout( self.table_layout )
|
|---|
| 216 | filters_widget.setLayout( self.filters_layout )
|
|---|
| 217 | #filters_widget.hide()
|
|---|
| 218 | self.set_admin( admin )
|
|---|
| 219 | self.splitter.addWidget( table_widget )
|
|---|
| 220 | self.splitter.addWidget( filters_widget )
|
|---|
| 221 | self.setLayout( widget_layout )
|
|---|
| 222 | self.closeAfterValidation = QtCore.SIGNAL( 'closeAfterValidation()' )
|
|---|
| 223 | self.search_filter = lambda q: q
|
|---|
| 224 | shortcut = QtGui.QShortcut(QtGui.QKeySequence(QtGui.QKeySequence.Find), self)
|
|---|
| 225 | self.connect( shortcut, QtCore.SIGNAL( 'activated()' ), self.activate_search )
|
|---|
| 226 | # give the table widget focus to prevent the header and its search control to
|
|---|
| 227 | # receive default focus, as this would prevent the displaying of 'Search...' in the
|
|---|
| 228 | # search control, but this conflicts with the MDI, resulting in the window not
|
|---|
| 229 | # being active and the menus not to work properly
|
|---|
| 230 | #table_widget.setFocus( QtCore.Qt.OtherFocusReason )
|
|---|
| 231 | #self.setFocusProxy(table_widget)
|
|---|
| 232 | #self.setFocus( QtCore.Qt.OtherFocusReason )
|
|---|
| 233 | post( self.admin.get_subclass_tree, self.setSubclassTree )
|
|---|
| 234 |
|
|---|
| 235 | def activate_search(self):
|
|---|
| 236 | self.header.search.setFocus(QtCore.Qt.ShortcutFocusReason)
|
|---|
| 237 |
|
|---|
| 238 | @model_function
|
|---|
| 239 | def get_title( self ):
|
|---|
| 240 | return self.title_format % {'verbose_name_plural':self.admin.get_verbose_name_plural()}
|
|---|
| 241 |
|
|---|
| 242 | @gui_function
|
|---|
| 243 | def setSubclassTree( self, subclasses ):
|
|---|
| 244 | if len( subclasses ) > 0:
|
|---|
| 245 | from inheritance import SubclassTree
|
|---|
| 246 | class_tree = SubclassTree( self.admin, self.splitter )
|
|---|
| 247 | self.splitter.insertWidget( 0, class_tree )
|
|---|
| 248 | self.connect( class_tree, SIGNAL( 'subclassClicked' ), self.set_admin )
|
|---|
| 249 |
|
|---|
| 250 | def sectionClicked( self, section ):
|
|---|
| 251 | """emits a row_selected signal"""
|
|---|
| 252 | self.emit( SIGNAL( 'row_selected' ), section )
|
|---|
| 253 |
|
|---|
| 254 | def copy_selected_rows( self ):
|
|---|
| 255 | """Copy the selected rows in this tableview"""
|
|---|
| 256 | logger.debug( 'delete selected rows called' )
|
|---|
| 257 | if self.table and self._table_model:
|
|---|
| 258 | for row in set( map( lambda x: x.row(), self.table.selectedIndexes() ) ):
|
|---|
| 259 | self._table_model.copy_row( row )
|
|---|
| 260 |
|
|---|
| 261 | def create_table_model( self, admin ):
|
|---|
| 262 | """Create a table model for the given admin interface"""
|
|---|
| 263 | return self.table_model( admin,
|
|---|
| 264 | admin.get_query,
|
|---|
| 265 | admin.get_columns )
|
|---|
| 266 |
|
|---|
| 267 | @gui_function
|
|---|
| 268 | def set_admin( self, admin ):
|
|---|
| 269 | """Switch to a different subclass, where admin is the admin object of the
|
|---|
| 270 | subclass"""
|
|---|
| 271 | logger.debug('set_admin called')
|
|---|
| 272 | self.admin = admin
|
|---|
| 273 | if self.table:
|
|---|
| 274 | self.disconnect(self._table_model, QtCore.SIGNAL( 'layoutChanged()' ), self.tableLayoutChanged )
|
|---|
| 275 | self.table_layout.removeWidget(self.table)
|
|---|
| 276 | self.table.deleteLater()
|
|---|
| 277 | self._table_model.deleteLater()
|
|---|
| 278 | self.table = self.table_widget( self.splitter )
|
|---|
| 279 | self._table_model = self.create_table_model( admin )
|
|---|
| 280 | self.table.setModel( self._table_model )
|
|---|
| 281 | self.connect( self.table.verticalHeader(),
|
|---|
| 282 | SIGNAL( 'sectionClicked(int)' ),
|
|---|
| 283 | self.sectionClicked )
|
|---|
| 284 | self.connect( self._table_model, QtCore.SIGNAL( 'layoutChanged()' ), self.tableLayoutChanged )
|
|---|
| 285 | self.tableLayoutChanged()
|
|---|
| 286 | self.table_layout.insertWidget( 1, self.table )
|
|---|
| 287 |
|
|---|
| 288 | def get_filters_and_actions():
|
|---|
| 289 | return ( admin.get_filters(), admin.get_list_actions() )
|
|---|
| 290 |
|
|---|
| 291 | post( get_filters_and_actions, self.set_filters_and_actions )
|
|---|
| 292 | post( admin.get_list_charts, self.setCharts )
|
|---|
| 293 |
|
|---|
| 294 | @gui_function
|
|---|
| 295 | def tableLayoutChanged( self ):
|
|---|
| 296 | logger.debug('tableLayoutChanged')
|
|---|
| 297 | if self.header:
|
|---|
| 298 | self.header.setNumberOfRows( self._table_model.rowCount() )
|
|---|
| 299 | item_delegate = self._table_model.getItemDelegate()
|
|---|
| 300 | if item_delegate:
|
|---|
| 301 | self.table.setItemDelegate( item_delegate )
|
|---|
| 302 | for i in range( self._table_model.columnCount() ):
|
|---|
| 303 | self.table.setColumnWidth( i, max( self._table_model.headerData( i, Qt.Horizontal, Qt.SizeHintRole ).toSize().width(),
|
|---|
| 304 | self.table.columnWidth( i ) ) )
|
|---|
| 305 |
|
|---|
| 306 | @gui_function
|
|---|
| 307 | def setCharts( self, charts ):
|
|---|
| 308 | """creates and display charts"""
|
|---|
| 309 | pass
|
|---|
| 310 | # if charts:
|
|---|
| 311 | #
|
|---|
| 312 | # from matplotlib.figure import Figure
|
|---|
| 313 | # from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as \
|
|---|
| 314 | # FigureCanvas
|
|---|
| 315 | #
|
|---|
| 316 | # chart = charts[0]
|
|---|
| 317 | #
|
|---|
| 318 | # def getData():
|
|---|
| 319 | # """fetches data for chart"""
|
|---|
| 320 | # from sqlalchemy.sql import select, func
|
|---|
| 321 | # from elixir import session
|
|---|
| 322 | # xcol = getattr( self.admin.entity, chart['x'] )
|
|---|
| 323 | # ycol = getattr( self.admin.entity, chart['y'] )
|
|---|
| 324 | # session.bind = self.admin.entity.table.metadata.bind
|
|---|
| 325 | # result = session.execute( select( [xcol, func.sum( ycol )] ).group_by( xcol ) )
|
|---|
| 326 | # summary = result.fetchall()
|
|---|
| 327 | # return [s[0] for s in summary], [s[1] for s in summary]
|
|---|
| 328 | #
|
|---|
| 329 | # class MyMplCanvas( FigureCanvas ):
|
|---|
| 330 | # """Ultimately, this is a QWidget (as well as a FigureCanvasAgg)"""
|
|---|
| 331 | #
|
|---|
| 332 | # def __init__( self, parent = None, width = 5, height = 4, dpi = 100 ):
|
|---|
| 333 | # fig = Figure( figsize = ( width, height ), dpi = dpi, facecolor = 'w' )
|
|---|
| 334 | # self.axes = fig.add_subplot( 111, axisbg = 'w' )
|
|---|
| 335 | # # We want the axes cleared every time plot() is called
|
|---|
| 336 | # self.axes.hold( False )
|
|---|
| 337 | # self.compute_initial_figure()
|
|---|
| 338 | # FigureCanvas.__init__( self, fig )
|
|---|
| 339 | # self.setParent( parent )
|
|---|
| 340 | # FigureCanvas.setSizePolicy( self,
|
|---|
| 341 | # QSizePolicy.Expanding,
|
|---|
| 342 | # QSizePolicy.Expanding )
|
|---|
| 343 | # FigureCanvas.updateGeometry( self )
|
|---|
| 344 | #
|
|---|
| 345 | #
|
|---|
| 346 | # def compute_initial_figure( self ):
|
|---|
| 347 | # pass
|
|---|
| 348 | #
|
|---|
| 349 | # def setData( data ):
|
|---|
| 350 | # """set chart data"""
|
|---|
| 351 | #
|
|---|
| 352 | # class MyStaticMplCanvas( MyMplCanvas ):
|
|---|
| 353 | # """simple canvas with a sine plot"""
|
|---|
| 354 | #
|
|---|
| 355 | # def compute_initial_figure( self ):
|
|---|
| 356 | # """computes initial figure"""
|
|---|
| 357 | # x, y = data
|
|---|
| 358 | # bar_positions = [i - 0.25 for i in range( 1, len( x ) + 1 )]
|
|---|
| 359 | # width = 0.5
|
|---|
| 360 | # self.axes.bar( bar_positions, y, width, color = 'b' )
|
|---|
| 361 | # self.axes.set_xlabel( 'Year' )
|
|---|
| 362 | # self.axes.set_ylabel( 'Sales' )
|
|---|
| 363 | # self.axes.set_xticks( range( len( x ) + 1 ) )
|
|---|
| 364 | # self.axes.set_xticklabels( [''] + [str( d ) for d in x] )
|
|---|
| 365 | #
|
|---|
| 366 | # sc = MyStaticMplCanvas( self, width = 5, height = 4, dpi = 100 )
|
|---|
| 367 | # self.table_layout.addWidget( sc )
|
|---|
| 368 | #
|
|---|
| 369 | # self.admin.mt.post( getData, setData )
|
|---|
| 370 |
|
|---|
| 371 | def deleteSelectedRows( self ):
|
|---|
| 372 | """delete the selected rows in this tableview"""
|
|---|
| 373 | logger.debug( 'delete selected rows called' )
|
|---|
| 374 | for row in set( map( lambda x: x.row(), self.table.selectedIndexes() ) ):
|
|---|
| 375 | self._table_model.removeRow( row )
|
|---|
| 376 |
|
|---|
| 377 | @gui_function
|
|---|
| 378 | def newRow( self ):
|
|---|
| 379 | """Create a new row in the tableview"""
|
|---|
| 380 | from camelot.view.workspace import get_workspace
|
|---|
| 381 | workspace = get_workspace()
|
|---|
| 382 | form = self.admin.create_new_view( workspace,
|
|---|
| 383 | oncreate = lambda o:self._table_model.insertEntityInstance( 0, o ),
|
|---|
| 384 | onexpunge = lambda o:self._table_model.removeEntityInstance( o ) )
|
|---|
| 385 | workspace.addSubWindow( form )
|
|---|
| 386 | form.show()
|
|---|
| 387 |
|
|---|
| 388 |
|
|---|
| 389 |
|
|---|
| 390 | # @gui_function
|
|---|
| 391 | # def set_filters_and_actions( self, filters_and_actions ):
|
|---|
| 392 | # """sets filters for the tableview"""
|
|---|
| 393 | # filters, actions = filters_and_actions
|
|---|
| 394 | # from filterlist import FilterList
|
|---|
| 395 | # from actionsbox import ActionsBox
|
|---|
| 396 | # logger.debug( 'setting filters for tableview' )
|
|---|
| 397 | # if self.filters:
|
|---|
| 398 | # self.disconnect( self.filters, SIGNAL( 'filters_changed' ), self.rebuildQuery )
|
|---|
| 399 | # self.filters.deleteLater()
|
|---|
| 400 | # self.filters = None
|
|---|
| 401 | # if self.actions:
|
|---|
| 402 | # self.actions.deleteLater()
|
|---|
| 403 | # self.actions = None
|
|---|
| 404 | # if filters:
|
|---|
| 405 | # self.filters = FilterList( filters, parent=self )
|
|---|
| 406 | # self.splitter.insertWidget( 2, self.filters )
|
|---|
| 407 | # self.connect( self.filters, SIGNAL( 'filters_changed' ), self.rebuildQuery )
|
|---|
| 408 | # if actions:
|
|---|
| 409 | # self.actions = ActionsBox( self, self._table_model.collection_getter, lambda:[] )
|
|---|
| 410 | # self.actions.setActions( actions )
|
|---|
| 411 | # self.splitter.insertWidget( 2, self.filters )
|
|---|
| 412 |
|
|---|
| 413 | def toHtml( self ):
|
|---|
| 414 | """generates html of the table"""
|
|---|
| 415 | table = [[getattr( row, col[0] ) for col in self.admin.getColumns()]
|
|---|
| 416 | for row in self.admin.entity.query.all()]
|
|---|
| 417 | context = {
|
|---|
| 418 | 'title': self.admin.get_verbose_name_plural(),
|
|---|
| 419 | 'table': table,
|
|---|
| 420 | 'columns': [c[0] for c in self.admin.getColumns()],
|
|---|
| 421 | }
|
|---|
| 422 | from camelot.view.templates import loader
|
|---|
| 423 | from jinja import Environment, FileSystemLoader
|
|---|
| 424 | env = Environment( loader = loader )
|
|---|
| 425 | tp = env.get_template( 'table_view.html' )
|
|---|
| 426 | return tp.render( context )
|
|---|
| 427 |
|
|---|
| 428 | def closeEvent( self, event ):
|
|---|
| 429 | """reimplements close event"""
|
|---|
| 430 | logger.debug( 'tableview closed' )
|
|---|
| 431 | # remove all references we hold, to enable proper garbage collection
|
|---|
| 432 | del self.table_layout
|
|---|
| 433 | del self.table
|
|---|
| 434 | del self.filters
|
|---|
| 435 | del self._table_model
|
|---|
| 436 | event.accept()
|
|---|
| 437 |
|
|---|
| 438 | def importWizard(self, attributes):
|
|---|
| 439 | from camelot.view.wizard.import_data import ImportWizard
|
|---|
| 440 | #object_attributes = ['title', 'releasedate', 'name', 'description' ]
|
|---|
| 441 | object_attributes = self.admin.entity().Admin.form_display.get_fields()
|
|---|
| 442 | importWizard = ImportWizard( self, object_attributes )
|
|---|
| 443 | importWizard.start()
|
|---|
| 444 | data = importWizard.getImportedData()
|
|---|
| 445 |
|
|---|
| 446 | def selectTableRow( self, row ):
|
|---|
| 447 | """selects the specified row"""
|
|---|
| 448 | self.table.selectRow( row )
|
|---|
| 449 |
|
|---|
| 450 | def makeImport():
|
|---|
| 451 | pass
|
|---|
| 452 | # for row in data:
|
|---|
| 453 | # o = self.admin.entity()
|
|---|
| 454 | # #For example, setattr(x, 'foobar', 123) is equivalent to x.foobar = 123
|
|---|
| 455 | # # if you want to import all attributes, you must link them to other objects
|
|---|
| 456 | # #for example: a movie has a director, this isn't a primitive like a string
|
|---|
| 457 | # # but a object fetched from the db
|
|---|
| 458 | # setattr(o, object_attributes[0], row[0])
|
|---|
| 459 | # name = row[2].split( ' ' ) #director
|
|---|
| 460 | # o.short_description = "korte beschrijving"
|
|---|
| 461 | # o.genre = ""
|
|---|
| 462 | # from sqlalchemy.orm.session import Session
|
|---|
| 463 | # Session.object_session(o).flush([o])
|
|---|
| 464 |
|
|---|
| 465 | post( makeImport )
|
|---|
| 466 |
|
|---|
| 467 | def selectedTableIndexes( self ):
|
|---|
| 468 | """returns a list of selected rows indexes"""
|
|---|
| 469 | return self.table.selectedIndexes()
|
|---|
| 470 |
|
|---|
| 471 | def getColumns( self ):
|
|---|
| 472 | """return the columns to be displayed in the table view"""
|
|---|
| 473 | return self.admin.get_columns()
|
|---|
| 474 |
|
|---|
| 475 | def getData( self ):
|
|---|
| 476 | """generator for data queried by table model"""
|
|---|
| 477 | for d in self._table_model.getData():
|
|---|
| 478 | yield d
|
|---|
| 479 |
|
|---|
| 480 | def getTitle( self ):
|
|---|
| 481 | """return the name of the entity managed by the admin attribute"""
|
|---|
| 482 | return self.admin.get_verbose_name()
|
|---|
| 483 |
|
|---|
| 484 | def viewFirst( self ):
|
|---|
| 485 | """selects first row"""
|
|---|
| 486 | self.selectTableRow( 0 )
|
|---|
| 487 |
|
|---|
| 488 | def viewLast( self ):
|
|---|
| 489 | """selects last row"""
|
|---|
| 490 | self.selectTableRow( self._table_model.rowCount() - 1 )
|
|---|
| 491 |
|
|---|
| 492 | def viewNext( self ):
|
|---|
| 493 | """selects next row"""
|
|---|
| 494 | first = self.selectedTableIndexes()[0]
|
|---|
| 495 | next = ( first.row() + 1 ) % self._table_model.rowCount()
|
|---|
| 496 | self.selectTableRow( next )
|
|---|
| 497 |
|
|---|
| 498 | def viewPrevious( self ):
|
|---|
| 499 | """selects previous row"""
|
|---|
| 500 | first = self.selectedTableIndexes()[0]
|
|---|
| 501 | prev = ( first.row() - 1 ) % self._table_model.rowCount()
|
|---|
| 502 | self.selectTableRow( prev )
|
|---|
| 503 |
|
|---|
| 504 | def rebuildQuery( self ):
|
|---|
| 505 | """resets the table model query"""
|
|---|
| 506 |
|
|---|
| 507 | def rebuild_query():
|
|---|
| 508 | query = self.admin.entity.query
|
|---|
| 509 | if self.filters:
|
|---|
| 510 | query = self.filters.decorate_query( query )
|
|---|
| 511 | if self.search_filter:
|
|---|
| 512 | query = self.search_filter( query )
|
|---|
| 513 | query_getter = lambda:query
|
|---|
| 514 | return query_getter
|
|---|
| 515 |
|
|---|
| 516 | post( rebuild_query, self._table_model.setQuery )
|
|---|
| 517 |
|
|---|
| 518 | def startSearch( self, text ):
|
|---|
| 519 | """rebuilds query based on filtering text"""
|
|---|
| 520 | from camelot.view.search import create_entity_search_query_decorator
|
|---|
| 521 | logger.debug( 'search %s' % text )
|
|---|
| 522 | self.search_filter = create_entity_search_query_decorator( self.admin, text )
|
|---|
| 523 | self.rebuildQuery()
|
|---|
| 524 |
|
|---|
| 525 | def cancelSearch( self ):
|
|---|
| 526 | """resets search filtering to default"""
|
|---|
| 527 | logger.debug( 'cancel search' )
|
|---|
| 528 | self.search_filter = lambda q: q
|
|---|
| 529 | self.rebuildQuery()
|
|---|
| 530 |
|
|---|
| 531 | @gui_function
|
|---|
| 532 | def set_filters_and_actions( self, filters_and_actions ):
|
|---|
| 533 | """sets filters for the tableview"""
|
|---|
| 534 | filters, actions = filters_and_actions
|
|---|
| 535 | from filterlist import FilterList
|
|---|
| 536 | from actionsbox import ActionsBox
|
|---|
| 537 | logger.debug( 'setting filters for tableview' )
|
|---|
| 538 |
|
|---|
| 539 | if self.filters:
|
|---|
| 540 | self.disconnect( self.filters, SIGNAL( 'filters_changed' ), self.rebuildQuery )
|
|---|
| 541 | self.filters_layout.removeWidget(self.filters)
|
|---|
| 542 | self.filters.deleteLater()
|
|---|
| 543 | self.filters = None
|
|---|
| 544 | if self.actions:
|
|---|
| 545 | self.filters_layout.removeWidget(self.actions)
|
|---|
| 546 | self.actions.deleteLater()
|
|---|
| 547 | self.actions = None
|
|---|
| 548 | if filters:
|
|---|
| 549 | self.filters = FilterList( filters, parent=self.splitter )
|
|---|
| 550 | self.filters_layout.addWidget( self.filters )
|
|---|
| 551 | self.connect( self.filters, SIGNAL( 'filters_changed' ), self.rebuildQuery )
|
|---|
| 552 | if actions:
|
|---|
| 553 |
|
|---|
| 554 | def selection_getter():
|
|---|
| 555 | selection = []
|
|---|
| 556 | for row in set( map( lambda x: x.row(), self.table.selectedIndexes() ) ):
|
|---|
| 557 | selection.append( self._table_model._get_object(row) )
|
|---|
| 558 | return selection
|
|---|
| 559 |
|
|---|
| 560 | self.actions = ActionsBox( self,
|
|---|
| 561 | self._table_model.get_collection_getter(),
|
|---|
| 562 | selection_getter )
|
|---|
| 563 |
|
|---|
| 564 | self.actions.setActions( actions )
|
|---|
| 565 | self.filters_layout.addWidget( self.actions )
|
|---|
| 566 |
|
|---|
| 567 | def toHtml( self ):
|
|---|
| 568 | """generates html of the table"""
|
|---|
| 569 | table = [[getattr( row, col[0] ) for col in self.admin.get_columns()]
|
|---|
| 570 | for row in self.admin.entity.query.all()]
|
|---|
| 571 | context = {
|
|---|
| 572 | 'title': self.admin.get_verbose_name_plural(),
|
|---|
| 573 | 'table': table,
|
|---|
| 574 | 'columns': [field_attributes['name'] for _field, field_attributes in self.admin.get_columns()],
|
|---|
| 575 | }
|
|---|
| 576 | from camelot.view.templates import loader
|
|---|
| 577 | from jinja import Environment
|
|---|
| 578 | env = Environment( loader = loader )
|
|---|
| 579 | tp = env.get_template( 'table_view.html' )
|
|---|
| 580 | return tp.render( context )
|
|---|
| 581 |
|
|---|
| 582 | def closeEvent( self, event ):
|
|---|
| 583 | """reimplements close event"""
|
|---|
| 584 | logger.debug( 'tableview closed' )
|
|---|
| 585 | # remove all references we hold, to enable proper garbage collection
|
|---|
| 586 | del self.table_layout
|
|---|
| 587 | del self.table
|
|---|
| 588 | del self.filters
|
|---|
| 589 | del self._table_model
|
|---|
| 590 | event.accept()
|
|---|
| 591 |
|
|---|
| 592 | def importWizard(self, attributes):
|
|---|
| 593 | from camelot.view.wizard.import_data import ImportWizard
|
|---|
| 594 | object_attributes = ['title', 'releasedate', 'name', 'description' ]
|
|---|
| 595 | importWizard = ImportWizard( self, object_attributes )
|
|---|
| 596 | importWizard.start()
|
|---|
| 597 | data = importWizard.getImportedData()
|
|---|
| 598 | def makeImport():
|
|---|
| 599 | for row in data:
|
|---|
| 600 | # get all possible fields (=attributes) from this object
|
|---|
| 601 | #attributes = o.Admin.form_display.get_fields()
|
|---|
| 602 | #object_attributes = ['title', 'releasedate', 'name', 'description' ]
|
|---|
| 603 | #print attributes
|
|---|
| 604 | # set title
|
|---|
| 605 | o = self.admin.entity()
|
|---|
| 606 | #For example, setattr(x, 'foobar', 123) is equivalent to x.foobar = 123
|
|---|
| 607 | setattr(o, object_attributes[0], row[0])
|
|---|
| 608 | #movie.title = row[0]
|
|---|
| 609 | name = row[2].split( ' ' ) #director
|
|---|
| 610 | #director = Person()
|
|---|
| 611 | #director.first_name = name[0]
|
|---|
| 612 | #director.last_name = name[1]
|
|---|
| 613 | #movie.director = director
|
|---|
| 614 | o.short_description = "korte beschrijving"
|
|---|
| 615 | #date = row[1].split('/') # date 12/03/2009
|
|---|
| 616 | #o.releasedate = datetime.date(year=int(date[2]), month=int(date[1]), day=int(date[0]))
|
|---|
| 617 | o.genre = ""
|
|---|
| 618 | #print o[attributes[0]]
|
|---|
| 619 | from sqlalchemy.orm.session import Session
|
|---|
| 620 | Session.object_session(o).flush([o])
|
|---|
| 621 |
|
|---|
| 622 | post( makeImport )
|
|---|
| 623 |
|
|---|
| 624 | def importFromFile( self ):
|
|---|
| 625 | """"import data : the data will be imported in the activeMdiChild """
|
|---|
| 626 | logger.info( 'call import method' )
|
|---|
| 627 | from camelot.view.wizard.importwizard import ImportWizard
|
|---|
| 628 | wizard = ImportWizard(self, self.admin)
|
|---|
| 629 | wizard.exec_()
|
|---|