Source code for camelot.view.controls.formview

#  ============================================================================
#
#  Copyright (C) 2007-2011 Conceptive Engineering bvba. All rights reserved.
#  www.conceptive.be / project-camelot@conceptive.be
#
#  This file is part of the Camelot Library.
#
#  This file may be used under the terms of the GNU General Public
#  License version 2.0 as published by the Free Software Foundation
#  and appearing in the file license.txt included in the packaging of
#  this file.  Please review this information to ensure GNU
#  General Public Licensing requirements will be met.
#
#  If you are unsure which license is appropriate for your use, please
#  visit www.python-camelot.com or contact project-camelot@conceptive.be
#
#  This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
#  WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
#
#  For use of this library in commercial applications, please contact
#  project-camelot@conceptive.be
#
#  ============================================================================

"""form view"""

import functools
import logging
LOGGER = logging.getLogger('camelot.view.controls.formview')

from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4.QtCore import Qt

from camelot.admin.action.form_action import FormActionGuiContext
from camelot.view.art import Icon
from camelot.view.model_thread import post
from camelot.view.model_thread import model_function
from camelot.view.controls.view import AbstractView
from camelot.view.controls.statusbar import StatusBar
from camelot.view import register
from camelot.view.action import ActionFactory

[docs]class ContextMenuAction(QtGui.QAction): default_icon = Icon('tango/16x16/categories/applications-system.png') def __init__(self, parent, title, icon = None): """ :param parent: the widget on which the context menu will be placed :param title: text displayed in the context menu :param icon: camelot.view.art.Icon object """ super(ContextMenuAction, self).__init__(title, parent) self.icon = icon if self.icon: self.setIcon(self.icon.getQIcon()) else: self.setIcon(self.default_icon.getQIcon())
[docs]class FormEditors( object ): """A class that holds the editors used on a form """ option = None bold_font = None def __init__( self, columns, widget_mapper, delegate, admin ): if self.option == None: self.option = QtGui.QStyleOptionViewItem() # set version to 5 to indicate the widget will appear on a # a form view and not on a table view self.option.version = 5 self.bold_font = QtGui.QApplication.font() self.bold_font.setBold(True) self._admin = admin self._widget_mapper = widget_mapper self._field_attributes = dict() self._index = dict() for i, (field_name, field_attributes ) in enumerate( columns): self._field_attributes[field_name] = field_attributes self._index[field_name] = i
[docs] def create_editor( self, field_name, parent ): """ :return: a :class:`QtGuiQWidget` or None if field_name is unknown """ index = self._index[field_name] model = self._widget_mapper.model() delegate = self._widget_mapper.itemDelegate() model_index = model.index( self._widget_mapper.currentIndex(), index ) widget_editor = delegate.createEditor( parent, self.option, model_index ) widget_editor.setObjectName('%s_editor'%field_name) delegate.setEditorData( widget_editor, model_index ) self._widget_mapper.addMapping( widget_editor, index ) return widget_editor
[docs] def create_label( self, field_name, editor, parent ): from camelot.view.controls.field_label import FieldLabel from camelot.view.controls.editors.wideeditor import WideEditor field_attributes = self._field_attributes[field_name] hide_title = field_attributes.get( 'hide_title', False ) widget_label = None if not hide_title: widget_label = FieldLabel( field_name, field_attributes['name'], field_attributes, self._admin ) widget_label.setObjectName('%s_label'%field_name) if not isinstance(editor, WideEditor): widget_label.setAlignment(Qt.AlignVCenter | Qt.AlignRight) # required fields font is bold nullable = field_attributes.get( 'nullable', True ) if not nullable: widget_label.setFont( self.bold_font ) return widget_label
[docs]class FormWidget(QtGui.QWidget): """A form widget comes inside a form view or inside an embedded manytoone editor""" changed_signal = QtCore.pyqtSignal( int ) def __init__(self, parent, admin): QtGui.QWidget.__init__(self, parent) self._admin = admin widget_mapper = QtGui.QDataWidgetMapper(self) widget_mapper.setObjectName('widget_mapper') if self._admin.get_save_mode()=='on_leave': widget_mapper.setSubmitPolicy(QtGui.QDataWidgetMapper.ManualSubmit) widget_layout = QtGui.QHBoxLayout() widget_layout.setSpacing(0) widget_layout.setContentsMargins(0, 0, 0, 0) self._index = 0 self._model = None self._form = None self._columns = None self._delegate = None self.setLayout(widget_layout)
[docs] def get_model(self): return self._model
[docs] def set_model(self, model): self._model = model self._model.dataChanged.connect( self._data_changed ) self._model.layoutChanged.connect( self._layout_changed ) self._model.item_delegate_changed_signal.connect( self._item_delegate_changed ) self._model.setObjectName( 'model' ) widget_mapper = self.findChild(QtGui.QDataWidgetMapper, 'widget_mapper' ) if widget_mapper: widget_mapper.setModel( model ) register.register( model, widget_mapper ) def get_columns_and_form(): return (self._model.getColumns(), self._admin.get_form_display()) post(get_columns_and_form, self._set_columns_and_form)
[docs] def clear_mapping(self): widget_mapper = self.findChild(QtGui.QDataWidgetMapper, 'widget_mapper' ) if widget_mapper: widget_mapper.clearMapping()
@QtCore.pyqtSlot( QtCore.QModelIndex, QtCore.QModelIndex ) def _data_changed(self, index_from, index_to): #@TODO: only revert if this form is in the changed range widget_mapper = self.findChild(QtGui.QDataWidgetMapper, 'widget_mapper' ) if widget_mapper: widget_mapper.revert() self.changed_signal.emit( widget_mapper.currentIndex() ) @QtCore.pyqtSlot() def _layout_changed(self): widget_mapper = self.findChild(QtGui.QDataWidgetMapper, 'widget_mapper' ) if widget_mapper: widget_mapper.revert() self.changed_signal.emit( widget_mapper.currentIndex() ) @QtCore.pyqtSlot() def _item_delegate_changed(self): from camelot.view.controls.delegates.delegatemanager import \ DelegateManager self._delegate = self._model.getItemDelegate() self._delegate.setObjectName('delegate') assert self._delegate != None assert isinstance(self._delegate, DelegateManager) self._create_widgets()
[docs] def set_index(self, index): self._index = index widget_mapper = self.findChild(QtGui.QDataWidgetMapper, 'widget_mapper' ) if widget_mapper: widget_mapper.setCurrentIndex(self._index)
[docs] def get_index(self): widget_mapper = self.findChild(QtGui.QDataWidgetMapper, 'widget_mapper' ) if widget_mapper: return widget_mapper.currentIndex()
[docs] def submit(self): widget_mapper = self.findChild(QtGui.QDataWidgetMapper, 'widget_mapper' ) if widget_mapper: widget_mapper.submit()
[docs] def to_first(self): widget_mapper = self.findChild(QtGui.QDataWidgetMapper, 'widget_mapper' ) if widget_mapper: widget_mapper.toFirst() self.changed_signal.emit( widget_mapper.currentIndex() )
[docs] def to_last(self): widget_mapper = self.findChild(QtGui.QDataWidgetMapper, 'widget_mapper' ) if widget_mapper: widget_mapper.toLast() self.changed_signal.emit( widget_mapper.currentIndex() )
[docs] def to_next(self): widget_mapper = self.findChild(QtGui.QDataWidgetMapper, 'widget_mapper' ) if widget_mapper: widget_mapper.toNext() self.changed_signal.emit( widget_mapper.currentIndex() )
[docs] def to_previous(self): widget_mapper = self.findChild(QtGui.QDataWidgetMapper, 'widget_mapper' ) if widget_mapper: widget_mapper.toPrevious() self.changed_signal.emit( widget_mapper.currentIndex() )
[docs] def export_ooxml(self): from camelot.view.export.word import open_stream_in_word def create_ooxml_export(row): # print self._columns def ooxml_export(): # TODO insert delegates fields = self._admin.get_all_fields_and_attributes() delegates = {} for field_name, attributes in fields.items(): delegates[field_name] = attributes['delegate'](**attributes) obj = self._model._get_object(row) document = self._form.render_ooxml(obj, delegates) open_stream_in_word( document ) return ooxml_export post(create_ooxml_export(self.get_index()))
@QtCore.pyqtSlot(tuple) def _set_columns_and_form(self, columns_and_form ): self._columns, self._form = columns_and_form self._create_widgets() def _create_widgets(self): """Create value and label widgets""" # # Dirty trick to make form views work during unit tests, since unit # tests have no event loop running, so the delegate will never be set, # so we get it and are sure it will be there if we are running without # threads # if not self._delegate: self._delegate = self._model.getItemDelegate() # # end of dirty trick # # only if all information is available, we can start building the form if not (self._form and self._columns and self._delegate): return widgets = {} widget_mapper = self.findChild(QtGui.QDataWidgetMapper, 'widget_mapper' ) if not widget_mapper: return LOGGER.debug( 'begin creating widgets' ) widget_mapper.setItemDelegate(self._delegate) widgets = FormEditors( self._columns, widget_mapper, self._delegate, self._admin ) widget_mapper.setCurrentIndex( self._index ) LOGGER.debug( 'put widgets on form' ) self.layout().insertWidget(0, self._form.render( widgets, self, True) ) LOGGER.debug( 'done' ) #self._widget_layout.setContentsMargins(7, 7, 7, 7)
[docs]class FormView(AbstractView): """A FormView is the combination of a FormWidget, possible actions and menu items .. form_widget: The class to be used as a the form widget inside the form view""" form_widget = FormWidget def __init__(self, title, admin, model, index): AbstractView.__init__(self) layout = QtGui.QVBoxLayout() form_and_actions_layout = QtGui.QHBoxLayout() form_and_actions_layout.setObjectName('form_and_actions_layout') layout.addLayout( form_and_actions_layout ) self.model = model self.admin = admin self.title_prefix = title form = FormWidget(self, admin) form.setObjectName( 'form' ) form.changed_signal.connect( self.update_title ) form.set_model(model) form.set_index(index) form_and_actions_layout.addWidget(form) statusbar = StatusBar(self) statusbar.setObjectName('statusbar') statusbar.setSizeGripEnabled(False) layout.addWidget(statusbar) layout.setAlignment(statusbar, Qt.AlignBottom) self.setLayout(layout) self.change_title(title) if hasattr(admin, 'form_size') and admin.form_size: self.setMinimumSize(admin.form_size[0], admin.form_size[1]) self.validator = admin.create_validator(model) self.validate_before_close = True def get_actions(): return admin.get_form_actions(None) post(get_actions, self.setActions) # # Define actions # self.setContextMenuPolicy(Qt.ActionsContextMenu) self.addAction( ActionFactory.view_first(self, self.viewFirst) ) self.addAction( ActionFactory.view_last(self, self.viewLast) ) self.addAction( ActionFactory.view_next(self, self.viewNext) ) self.addAction( ActionFactory.view_previous(self, self.viewPrevious) ) self.addAction( ActionFactory.refresh(self, self.refresh_session) ) # Disabled the export to Word feature until it's finish # self.addAction( ActionFactory.export_ooxml(self, form.export_ooxml) ) @QtCore.pyqtSlot()
[docs] def refresh_session(self): from elixir import session from camelot.core.orm import refresh_session post( functools.update_wrapper( functools.partial( refresh_session, session ), refresh_session ) )
@QtCore.pyqtSlot()
[docs] def refresh(self): """Refresh the data in the current view""" self.model.refresh()
def _get_title( self, index ): obj = self.model._get_object( index ) return u'%s %s' % ( self.title_prefix, self.admin.get_verbose_identifier(obj) ) @QtCore.pyqtSlot( int )
[docs] def update_title(self, current_index ): post( self._get_title, self.change_title, args=(current_index,) ) #def getEntity(self): #form = self.findChild(QtGui.QWidget, 'form' ) #if form: #return self.model._get_object(form.get_index())
@QtCore.pyqtSlot(list)
[docs] def setActions(self, actions): form = self.findChild(QtGui.QWidget, 'form' ) layout = self.findChild(QtGui.QLayout, 'form_and_actions_layout' ) if actions and form and layout: side_panel_layout = QtGui.QVBoxLayout() widget_mapper = self.findChild(QtGui.QDataWidgetMapper, 'widget_mapper' ) from camelot.view.controls.actionsbox import ActionsBox LOGGER.debug('setting Actions for formview') gui_context = FormActionGuiContext() gui_context.workspace = self gui_context.admin = self.admin gui_context.widget_mapper = widget_mapper actions_widget = ActionsBox( parent=self, gui_context=gui_context ) actions_widget.setObjectName('actions') actions_widget.set_actions( actions ) side_panel_layout.addWidget( actions_widget ) side_panel_layout.addStretch() layout.addLayout( side_panel_layout )
[docs] def viewFirst(self): """select model's first row""" form = self.findChild(QtGui.QWidget, 'form' ) if form: form.submit() form.to_first()
[docs] def viewLast(self): """select model's last row""" # submit should not happen a second time, since then we don't want # the widgets data to be written to the model form = self.findChild(QtGui.QWidget, 'form' ) if form: form.submit() form.to_last()
[docs] def viewNext(self): """select model's next row""" # submit should not happen a second time, since then we don't want # the widgets data to be written to the model form = self.findChild(QtGui.QWidget, 'form' ) if form: form.submit() form.to_next()
[docs] def viewPrevious(self): """select model's previous row""" # submit should not happen a second time, since then we don't want # the widgets data to be written to the model form = self.findChild(QtGui.QWidget, 'form' ) if form: form.submit() form.to_previous()
@QtCore.pyqtSlot(bool)
[docs] def showMessage(self, valid): form = self.findChild(QtGui.QWidget, 'form' ) if not valid and form: reply = self.validator.validityDialog( form.get_index(), self ).exec_() if reply == QtGui.QMessageBox.Discard: # clear mapping to prevent data being written again to the model, # then we reverted the row form.clear_mapping() self.model.revertRow(form.get_index()) self.validate_before_close = False self.close() else: self.validate_before_close = False self.close()
[docs] def validateClose(self): LOGGER.debug('validate before close : %s' % self.validate_before_close) form = self.findChild(QtGui.QWidget, 'form' ) if self.validate_before_close and form: # submit should not happen a second time, since then we don't # want the widgets data to be written to the model form.submit() def validate(): return self.validator.isValid(form.get_index()) post(validate, self.showMessage) return False return True
[docs] def closeEvent(self, event): #print 'close event' LOGGER.debug('formview closed') if self.validateClose(): event.accept() else: event.ignore()
@model_function
[docs] def toHtml(self): """generates html of the form""" from jinja2 import Environment def to_html(d = u''): """Jinja 1 filter to convert field values to their default html representation """ def wrapped_in_table(env, context, value): if isinstance(value, list): return u'<table><tr><td>' + \ u'</td></tr><tr><td>'.join( [unicode(e) for e in value] ) + u'</td></tr></table>' return unicode(value) return wrapped_in_table entity = self.getEntity() fields = self.admin.get_fields() table = [dict( field_attributes = field_attributes, value = getattr(entity, name )) for name, field_attributes in fields] context = { 'title': self.admin.get_verbose_name(), 'table': table, } from camelot.view.templates import loader env = Environment(loader = loader) env.filters['to_html'] = to_html tp = env.get_template('form_view.html') return tp.render(context)

Comments
blog comments powered by Disqus