Source code for camelot.view.action_steps.change_object

#  ============================================================================
#
#  Copyright (C) 2007-2016 Conceptive Engineering bvba.
#  www.conceptive.be / info@conceptive.be
#
#  Redistribution and use in source and binary forms, with or without
#  modification, are permitted provided that the following conditions are met:
#      * Redistributions of source code must retain the above copyright
#        notice, this list of conditions and the following disclaimer.
#      * Redistributions in binary form must reproduce the above copyright
#        notice, this list of conditions and the following disclaimer in the
#        documentation and/or other materials provided with the distribution.
#      * Neither the name of Conceptive Engineering nor the
#        names of its contributors may be used to endorse or promote products
#        derived from this software without specific prior written permission.
#  
#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
#  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
#  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
#  DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
#  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
#  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
#  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
#  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
#  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
#  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
#  ============================================================================

import six

from ...core.qt import QtCore, QtGui, QtWidgets
from ..workspace import apply_form_state

from camelot.admin.action import ActionStep
from camelot.admin.action.form_action import FormActionGuiContext
from camelot.core.exception import CancelRequest
from camelot.core.utils import ugettext_lazy as _
from camelot.core.utils import ugettext
from camelot.view.action_runner import hide_progress_dialog
from camelot.view.art import Icon
from camelot.view.controls import delegates, editors
from camelot.view.controls.formview import FormWidget
from camelot.view.controls.actionsbox import ActionsBox
from camelot.view.controls.standalone_wizard_page import StandaloneWizardPage
from camelot.view.model_thread import post
from camelot.view.proxy import ValueLoading
from camelot.view.proxy.collection_proxy import CollectionProxy

[docs]class ChangeObjectDialog( StandaloneWizardPage ): """A dialog to change an object. This differs from a FormView in that it does not contains Actions, and has an OK button that is enabled when the object is valid. :param obj: The object to change :param admin: The admin class used to create a form .. image:: /_static/actionsteps/change_object.png """ def __init__( self, obj, admin, form_display, columns, form_actions, accept, reject, title = _('Please complete'), subtitle = _('Complete the form and press the OK button'), icon = Icon('tango/22x22/categories/preferences-system.png'), parent=None, flags=QtCore.Qt.Dialog ): super(ChangeObjectDialog, self).__init__( '', parent, flags ) self.setWindowTitle( admin.get_verbose_name() ) self.set_banner_logo_pixmap( icon.getQPixmap() ) self.set_banner_title( six.text_type(title) ) self.set_banner_subtitle( six.text_type(subtitle) ) self.banner_widget().setStyleSheet('background-color: white;') model = CollectionProxy(admin) model.set_value([obj]) model.set_columns(columns) validator = model.get_validator() layout = QtWidgets.QHBoxLayout() layout.setObjectName( 'form_and_actions_layout' ) form_widget = FormWidget(admin=admin, model=model, form_display=form_display, columns=columns, parent=self) layout.addWidget( form_widget ) validator.validity_changed_signal.connect( self._validity_changed ) form_widget.setObjectName( 'form' ) if hasattr(admin, 'form_size') and admin.form_size: form_widget.setMinimumSize(admin.form_size[0], admin.form_size[1]) self.main_widget().setLayout(layout) self.gui_context = FormActionGuiContext() self.gui_context.workspace = self self.gui_context.admin = admin self.gui_context.view = self self.gui_context.widget_mapper = self.findChild( QtGui.QDataWidgetMapper, 'widget_mapper' ) cancel_button = QtWidgets.QPushButton(six.text_type(reject)) cancel_button.setObjectName( 'cancel' ) ok_button = QtWidgets.QPushButton(six.text_type(accept)) ok_button.setObjectName( 'ok' ) ok_button.setEnabled( False ) layout = QtWidgets.QHBoxLayout() layout.setDirection( QtWidgets.QBoxLayout.RightToLeft ) layout.addWidget( ok_button ) layout.addWidget( cancel_button ) layout.addStretch() self.buttons_widget().setLayout( layout ) cancel_button.pressed.connect( self.reject ) ok_button.pressed.connect( self.accept ) # do inital validation, so the validity changed signal is valid self._validity_changed( 0 ) # set the actions in the actions panel self.set_actions(form_actions) @QtCore.qt_slot(list) def set_actions(self, actions): layout = self.findChild(QtGui.QLayout, 'form_and_actions_layout' ) if actions and layout: side_panel_layout = QtWidgets.QVBoxLayout() actions_widget = ActionsBox( parent = self, gui_context = self.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 ) @QtCore.qt_slot(int) def _validity_changed(self, row): form = self.findChild( QtWidgets.QWidget, 'form' ) if not form: return model = form.get_model() def is_valid(): return model.get_validator().isValid(0) post(is_valid, self._change_complete) def _change_complete(self, complete): ok_button = self.findChild( QtWidgets.QPushButton, 'ok' ) cancel_button = self.findChild( QtWidgets.QPushButton, 'cancel' ) if ok_button != None: ok_button.setEnabled( complete ) ok_button.setDefault( complete ) if cancel_button != None: ok_button.setDefault( not complete )
[docs]class ChangeObjectsDialog( StandaloneWizardPage ): """A dialog to change a list of objects. This differs from a ListView in that it does not contains Actions, and has an OK button that is enabled when all objects are valid. :param objects: The object to change :param admin: The admin class used to create a form .. image:: /_static/actionsteps/change_object.png """ def __init__( self, objects, admin, parent = None, flags = QtCore.Qt.Window ): super(ChangeObjectsDialog, self).__init__( '', parent, flags ) self.banner_widget().setStyleSheet('background-color: white;') table_widget = editors.One2ManyEditor( admin = admin, parent = self, create_inline = True, ) model = table_widget.get_model() self.validator = model.get_validator() self.validator.validity_changed_signal.connect( self.update_complete ) model.layoutChanged.connect(self.validate_all_rows) table_widget.set_value(objects) table_widget.setObjectName( 'table_widget' ) note = editors.NoteEditor( parent=self ) note.set_value(None) note.setObjectName( 'note' ) layout = QtWidgets.QVBoxLayout() layout.addWidget( table_widget ) layout.addWidget( note ) self.main_widget().setLayout( layout ) self.set_default_buttons() ok_button = self.buttons_widget().findChild( QtWidgets.QPushButton, 'accept' ) ok_button.setEnabled( False ) @QtCore.qt_slot() def validate_all_rows(self): post(self.validator.validate_all_rows, self._all_rows_validated) def _all_rows_validated(self, *args): self.update_complete( 0 ) @QtCore.qt_slot(int) def update_complete(self, row=0): complete = (self.validator.number_of_invalid_rows()==0) note = self.findChild( QtWidgets.QWidget, 'note' ) ok = self.findChild( QtWidgets.QWidget, 'accept' ) if note != None and ok != None: ok.setEnabled( complete ) if complete: note.set_value( None ) else: first_row = self.validator.get_first_invalid_row() + 1 notes = [ ugettext('Please correct row {0} before proceeding.').format(first_row), ] notes.extend(self.validator.get_messages(first_row-1)) note.set_value(u'<br>'.join(notes))
[docs]class ChangeObject( ActionStep ): """ Pop up a form for the user to change an object :param obj: the object to change :param admin: an instance of an admin class to use to edit the object, None if the default is to be taken .. attribute:: accept The text shown in the accept button .. attribute:: reject The text shown in the reject button """ def __init__( self, obj, admin=None ): self.obj = obj self.admin = admin self.accept = _('OK') self.reject = _('Cancel')
[docs] def get_object( self ): """Use this method to get access to the object to change in unit tests :return: the object to change """ return self.obj
[docs] def render( self, gui_context ): """create the dialog. this method is used to unit test the action step.""" super(ChangeObject, self).gui_run(gui_context) dialog = ChangeObjectDialog(self.obj, self.admin, self.form_display, self.columns, self.form_actions, self.accept, self.reject) return dialog
def gui_run( self, gui_context ): dialog = self.render(gui_context) apply_form_state(dialog, None, self.admin.form_state) with hide_progress_dialog( gui_context ): result = dialog.exec_() if result == QtWidgets.QDialog.Rejected: raise CancelRequest() return self.obj def model_run(self, model_context): cls = self.obj.__class__ self.admin = self.admin or model_context.admin.get_related_admin( cls ) self.form_display = self.admin.get_form_display() self.columns = self.admin.get_fields() self.form_actions = self.admin.get_form_actions(None)
[docs]class ChangeObjects( ActionStep ): """ Pop up a list for the user to change objects :param objects: a list of objects to change :param admin: an instance of an admin class to use to edit the objects. .. image:: /_static/listactions/import_from_file_preview.png This action step can be customised using these attributes : .. attribute:: window_title the window title of the dialog shown .. attribute:: title the title of the dialog shown .. attribute:: subtitle the subtitle of the dialog shown .. attribute:: icon the :class:`camelot.view.art.Icon` in the top right corner of the dialog """ def __init__( self, objects, admin ): self.objects = objects self.admin = admin self.window_title = admin.get_verbose_name_plural() self.title = _('Data Preview') self.subtitle = _('Please review the data below.') self.icon = Icon('tango/32x32/mimetypes/x-office-spreadsheet.png')
[docs] def get_objects( self ): """Use this method to get access to the objects to change in unit tests :return: the object to change """ return self.objects
[docs] def render( self ): """create the dialog. this method is used to unit test the action step.""" dialog = ChangeObjectsDialog( self.objects, self.admin ) dialog.setWindowTitle( six.text_type( self.window_title ) ) dialog.set_banner_title( six.text_type( self.title ) ) dialog.set_banner_subtitle( six.text_type( self.subtitle ) ) dialog.set_banner_logo_pixmap( self.icon.getQPixmap() ) # # the dialog cannot estimate its size, so use 75% of screen estate # desktop = QtWidgets.QApplication.desktop() available_geometry = desktop.availableGeometry( dialog ) dialog.resize( available_geometry.width() * 0.75, available_geometry.height() * 0.75 ) return dialog
def gui_run( self, gui_context ): dialog = self.render() with hide_progress_dialog( gui_context ): result = dialog.exec_() if result == QtWidgets.QDialog.Rejected: raise CancelRequest() return self.objects
[docs]class ChangeFieldDialog( StandaloneWizardPage ): """A dialog to change a field of an object. """ def __init__( self, admin, field_attributes, field_name, parent = None, flags=QtCore.Qt.Dialog ): super(ChangeFieldDialog, self).__init__( '', parent, flags ) from camelot.view.controls.editors import ChoicesEditor self.field_attributes = field_attributes self.field = field_name self.value = None self.static_field_attributes = admin.get_static_field_attributes self.setWindowTitle( admin.get_verbose_name_plural() ) self.set_banner_title( _('Replace field contents') ) self.set_banner_subtitle( _('Select the field to update and enter its new value') ) self.banner_widget().setStyleSheet('background-color: white;') editor = ChoicesEditor( parent=self ) editor.setObjectName( 'field_choice' ) layout = QtWidgets.QVBoxLayout() layout.addWidget( editor ) self.main_widget().setLayout( layout ) def field_changeable(attributes): if not attributes.get('editable', False): return False if attributes.get('delegate', None) in (delegates.One2ManyDelegate,): return False return True choices = [(field, six.text_type(attributes['name'])) for field, attributes in six.iteritems(field_attributes) if field_changeable(attributes)] choices.sort( key = lambda choice:choice[1] ) editor.set_choices( choices + [(None,'')] ) editor.set_value(self.field) self.field_changed() editor.editingFinished.connect( self.field_changed ) self.set_default_buttons() if self.field is not None: value_editor = self.findChild(QtWidgets.QWidget, 'value_editor') if value_editor is not None: value_editor.setFocus() @QtCore.qt_slot() def field_changed(self): selected_field = ValueLoading editor = self.findChild( QtWidgets.QWidget, 'field_choice' ) value_editor = self.findChild( QtWidgets.QWidget, 'value_editor' ) if editor != None: selected_field = editor.get_value() if value_editor != None: value_editor.deleteLater() if selected_field not in (None, ValueLoading): self.field = selected_field self.value = None static_field_attributes = list(self.static_field_attributes([selected_field]))[0] # editable might be a dynamic field attribute static_field_attributes.setdefault('editable', True) delegate = static_field_attributes['delegate'](parent = self, **static_field_attributes) option = QtGui.QStyleOptionViewItem() option.version = 5 value_editor = delegate.createEditor( self, option, None ) value_editor.setObjectName( 'value_editor' ) value_editor.set_field_attributes( **static_field_attributes ) self.main_widget().layout().addWidget( value_editor ) value_editor.editingFinished.connect( self.value_changed ) value_editor.set_value(None) self.value_changed( value_editor ) def value_changed(self, value_editor=None): if not value_editor: value_editor = self.findChild( QtWidgets.QWidget, 'value_editor' ) if value_editor != None: self.value = value_editor.get_value()
[docs]class ChangeField( ActionStep ): """ Pop up a list of fields from an object a user can change. When the user selects a field, an appropriate widget is shown to change the value of that field. :param admin: the admin of the object of which to change the field :param field_attributes: a list of field attributes of the fields that can be changed. If `None` is given, all fields are shown. :param field_name: the name of the selected field when opening the dialog This action step returns a tuple with the name of the selected field, and its new value. """ def __init__(self, admin, field_attributes = None, field_name=None): super( ChangeField, self ).__init__() self.admin = admin self.field_name = field_name if field_attributes == None: field_attributes = admin.get_all_fields_and_attributes() self.field_attributes = field_attributes
[docs] def render( self ): """create the dialog. this method is used to unit test the action step.""" return ChangeFieldDialog( self.admin, self.field_attributes, self.field_name )
def gui_run( self, gui_context ): dialog = self.render() with hide_progress_dialog( gui_context ): result = dialog.exec_() if result == QtWidgets.QDialog.Rejected: raise CancelRequest() return (dialog.field, dialog.value)