root/trunk/camelot/view/controls/formview.py @ 1178

Revision 1178, 11.2 KB (checked in by erikj, 6 months ago)

add confirm_delete class attribute to ObjectAdmin?

Line 
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"""form view"""
29
30import logging
31logger = logging.getLogger( 'camelot.view.controls.formview' )
32
33from PyQt4 import QtCore
34from PyQt4.QtCore import Qt
35from PyQt4 import QtGui
36from camelot.view.model_thread import model_function, post
37from camelot.view.controls.view import AbstractView
38
39class FormView( AbstractView ):
40
41    def __init__( self, title, admin, model, index ):
42        AbstractView.__init__( self )
43        self.title_prefix = title
44        self.admin = admin
45        self.model = model
46        self.index = index
47        self.change_title(title)
48        self.widget_mapper = QtGui.QDataWidgetMapper()
49        self.widget_layout = QtGui.QHBoxLayout()
50        self.widget_layout.setSpacing( 0 )
51        self.widget_layout.setMargin( 0 )
52   
53        self.closeAfterValidation = QtCore.SIGNAL( 'closeAfterValidation()' )
54        sig = 'dataChanged(const QModelIndex &, const QModelIndex &)'
55        self.connect( self.model, QtCore.SIGNAL( sig ), self.dataChanged )
56        self.connect( self.model, QtCore.SIGNAL( 'layoutChanged()' ), self.layout_changed )
57        self.connect( self.model, self.model.item_delegate_changed_signal, self.item_delegate_changed )
58   
59        self.widget_mapper.setModel( model )
60        self.setLayout( self.widget_layout )
61   
62        if hasattr( admin, 'form_size' ) and admin.form_size:
63            self.setMinimumSize( admin.form_size[0], admin.form_size[1] )
64     
65        self.validator = admin.create_validator( model )
66        self.validate_before_close = True
67        self.form = None
68        self.columns = None
69        self.delegate = None
70   
71        def getColumnsAndForm():
72            return ( self.model.getColumns(), self.admin.get_form_display() )
73     
74        post( getColumnsAndForm, self.handleGetColumnsAndForm )
75   
76        def getActions():
77            return admin.get_form_actions( None )
78     
79        post( getActions, self.setActions )
80        self.update_title()
81   
82    def update_title( self ):
83 
84        def get_title():
85            obj = self.getEntity()
86            return u'%s %s' % ( self.title_prefix, self.admin.get_verbose_identifier( obj ) )
87     
88        post( get_title, self.change_title )
89   
90    def dataChanged( self, index_from, index_to ):
91        #@TODO: only revert if this form is in the changed range
92        self.widget_mapper.revert()
93        self.update_title()
94       
95    def layout_changed(self):
96        self.widget_mapper.revert()
97        self.update_title()       
98   
99    def handleGetColumnsAndForm( self, columns_and_form ):
100        self.columns, self.form = columns_and_form
101        self.setColumnsFormAndDelegate()
102
103    def item_delegate_changed(self):
104        from camelot.view.controls.delegates.delegatemanager import DelegateManager
105        self.delegate = self.model.getItemDelegate()
106        assert self.delegate
107        assert isinstance(self.delegate, DelegateManager)
108        self.setColumnsFormAndDelegate()
109   
110    def setColumnsFormAndDelegate( self ):
111        """Create value and label widgets"""
112        from camelot.view.controls.user_translatable_label import UserTranslatableLabel
113        from camelot.view.controls.editors.wideeditor import WideEditor
114        #
115        # Dirty trick to make form views work during unit tests, since unit tests
116        # have no event loop running, so the delegate will never be set, so we get
117        # it and are sure it will be there if we are running without threads
118        #
119        if not self.delegate:
120            self.delegate = self.model.getItemDelegate()
121        #
122        # end of dirty trick
123        #
124        # only if all information is available, we can start building the form
125        if not (self.form and self.columns and self.delegate):
126            return
127        widgets = {}
128        self.widget_mapper.setItemDelegate( self.delegate )
129        option = QtGui.QStyleOptionViewItem()
130        # set version to 5 to indicate the widget will appear on a
131        # a form view and not on a table view
132        option.version = 5
133   
134        for i, ( field_name, field_attributes ) in enumerate( self.columns ):
135            model_index = self.model.index( self.index, i )
136            hide_title = False
137            if 'hide_title' in field_attributes:
138                hide_title = field_attributes['hide_title']
139            widget_label = None
140            widget_editor = self.delegate.createEditor( self, option, model_index )
141            if not hide_title:
142                widget_label = UserTranslatableLabel( field_attributes['name'] )
143                if not isinstance(widget_editor, WideEditor):
144                    widget_label.setAlignment( Qt.AlignVCenter | Qt.AlignRight )
145     
146            # required fields font is bold
147            if ( 'nullable' in field_attributes ) and \
148               ( not field_attributes['nullable'] ):
149                font = QtGui.QApplication.font()
150                font.setBold( True )
151                widget_label.setFont( font )
152       
153            assert widget_editor
154            assert isinstance(widget_editor, QtGui.QWidget)
155           
156            self.widget_mapper.addMapping( widget_editor, i )
157            widgets[field_name] = ( widget_label, widget_editor )
158   
159        self.widget_mapper.setCurrentIndex( self.index )
160        self.widget_layout.insertWidget( 0, self.form.render( widgets, self ) )
161        self.widget_layout.setContentsMargins( 7, 7, 7, 7 )
162   
163    def getEntity( self ):
164        return self.model._get_object( self.widget_mapper.currentIndex() )
165   
166    def setActions( self, actions ):
167        if actions:
168            side_panel_layout = QtGui.QVBoxLayout()
169            from camelot.view.controls.actionsbox import ActionsBox
170            logger.debug( 'setting Actions for formview' )
171            self.actions_widget = ActionsBox( self, self.getEntity )
172            self.actions_widget.setActions( actions )
173            side_panel_layout.insertWidget( 1, self.actions_widget )
174            side_panel_layout.addStretch()
175            self.widget_layout.addLayout(side_panel_layout)
176     
177    def viewFirst( self ):
178        """select model's first row"""
179        # submit should not happen a second time, since then we don't want
180        # the widgets data to be written to the model
181        self.widget_mapper.submit()
182        self.widget_mapper.toFirst()
183        self.update_title()
184   
185    def viewLast( self ):
186        """select model's last row"""
187        # submit should not happen a second time, since then we don't want
188        # the widgets data to be written to the model
189        self.widget_mapper.submit()
190        self.widget_mapper.toLast()
191        self.update_title()
192   
193    def viewNext( self ):
194        """select model's next row"""
195        # submit should not happen a second time, since then we don't want
196        # the widgets data to be written to the model
197        self.widget_mapper.submit()
198        self.widget_mapper.toNext()
199        self.update_title()
200   
201    def viewPrevious( self ):
202        """select model's previous row"""
203        # submit should not happen a second time, since then we don't want
204        # the widgets data to be written to the model
205        self.widget_mapper.submit()
206        self.widget_mapper.toPrevious()
207        self.update_title()
208   
209    def showMessage( self, valid ):
210        import sip
211        if not valid:
212            reply = self.validator.validityDialog( self.widget_mapper.currentIndex(), self ).exec_()
213            if reply == QtGui.QMessageBox.Discard:
214            # clear mapping to prevent data being written again to the model,
215            # then we reverted the row
216                self.widget_mapper.clearMapping()
217                self.model.revertRow( self.widget_mapper.currentIndex() )
218                self.validate_before_close = False
219                self.emit( self.closeAfterValidation )
220        else:
221            self.validate_before_close = False
222            if not sip.isdeleted( self ):
223                self.emit( self.closeAfterValidation )
224                   
225    def validateClose( self ):
226        logger.debug( 'validate before close : %s' % self.validate_before_close )
227        if self.validate_before_close:
228            # submit should not happen a second time, since then we don't
229            # want the widgets data to be written to the model
230            self.widget_mapper.submit()
231     
232            def validate():
233                return self.validator.isValid( self.widget_mapper.currentIndex() )
234       
235            post( validate, self.showMessage )
236            return False
237     
238        return True
239   
240    def closeEvent( self, event ):
241        logger.debug( 'formview closed' )
242        if self.validateClose():
243            event.accept()
244        else:
245            event.ignore()
246     
247    @model_function
248    def toHtml( self ):
249        """generates html of the form"""
250        from jinja import Environment
251   
252        def to_html( d = u'' ):
253            """Jinja 1 filter to convert field values to their default html
254            representation
255            """
256     
257            def wrapped_in_table( env, context, value ):
258                if isinstance( value, list ):
259                    return u'<table><tr><td>' + \
260                           u'</td></tr><tr><td>'.join( [unicode( e ) for e in value] ) + \
261                           u'</td></tr></table>'
262                return unicode( value )
263       
264            return wrapped_in_table
265     
266        entity = self.getEntity()
267        fields = self.admin.get_fields()
268        table = [dict( field_attributes = field_attributes,
269                      value = getattr( entity, name ) )
270                      for name, field_attributes in fields]
271   
272        context = {
273          'title': self.admin.get_verbose_name(),
274          'table': table,
275        }
276   
277        from camelot.view.templates import loader
278        env = Environment( loader = loader )
279        env.filters['to_html'] = to_html
280        tp = env.get_template( 'form_view.html' )
281   
282        return tp.render( context )
Note: See TracBrowser for help on using the browser.