class FormWidget(QWidget, FormWidgetUi): closed = pyqtSignal() shortcutsChanged = pyqtSignal() def __init__(self, model, res_id=False, domain=None, view_type=None, view_ids=None, context=None, parent=None, name=False, readonly=False): """ Class constructor :param model: Name of the model the form should handle :param res_id: List of ids of type 'model' to load :param domain: Domain the models should be in :param view_type: type of view: form, tree, graph, calendar, ... :param view_ids: Id's of the views 'ir.ui.view' to show :param context: Context for the current data set :param parent: Parent widget of the form :param name: User visible title of the form """ QWidget.__init__(self, parent) FormWidgetUi.__init__(self) self.setupUi(self) self.readonly = readonly if domain is None: domain = [] if view_ids is None: view_ids = [] if context is None: context = {} # This variable holds the id used to update status (and show number of # attachments) # If it doesn't change we won't update the number of attachments, # avoiding some server calls. self.previousId = False self.previousAttachments = False # Workaround: In some cases (detected in some CRM actions) view_type # and view_ids may contain duplicate entries. Here we remove duplicates # (ensuring lists order is kept). if view_type: new_view_ids = [] new_view_type = [] for i in range(len(view_type)): if not view_type[i] in new_view_type: if i < len(view_ids): new_view_ids.append(view_ids[i]) new_view_type.append(view_type[i]) view_ids = new_view_ids view_type = new_view_type if not view_type: view_type = ['form', 'tree'] else: if view_type[0] in ['graph'] and not res_id: res_id = Rpc.session.execute( '/object', 'execute', model, 'search', domain) fields = {} self.model = model self.previousAction = None self.fields = fields self.domain = domain self.context = context self.viewTypes = view_type self.viewIds = view_ids self._switchViewMenu = QMenu(self) self._viewActionGroup = QActionGroup(self) self._viewActionGroup.setExclusive(True) for view in self.viewTypes: action = ViewFactory.viewAction(view, self) if not action: continue action.triggered.connect(self.switchView) self._switchViewMenu.addAction(action) self._viewActionGroup.addAction(action) self.group = RecordGroup(self.model, context=self.context) if Settings.value('koo.sort_mode') == 'visible_items': self.group.setSortMode(RecordGroup.SortVisibleItems) self.group.setDomain(domain) self.group.modified.connect(self.notifyRecordModified) self.screen.setRecordGroup(self.group) self.screen.setEmbedded(False) self.screen.activated.connect(self.switchToForm) # @xtorello toreview not-clear signal<->slot self.screen.currentChangedSignal.connect(self.updateStatus) self.screen.closed.connect(self.closeWidget) self.screen.recordMessage[int, int, int].connect(self.updateRecordStatus) self.screen.statusMessage['QString'].connect(self.updateStatus) self._allowOpenInNewWindow = True # Remove ids with False value self.screen.setupViews(view_type, view_ids) if name: self.name = name else: self.name = self.screen.currentView().title self.handlers = { 'New': self.new, 'Save': self.save, 'Export': self.export, 'Import': self.import_, 'Delete': self.remove, 'Find': self.search, 'Previous': self.previous, 'Next': self.__next__, 'GoToResourceId': self.goto, 'AccessLog': self.showLogs, 'Reload': self.reload, 'Switch': self.switchView, 'Attach': self.showAttachments, 'Duplicate': self.duplicate, 'BatchInsert': self.batchInsert, 'BatchUpdate': self.batchUpdate, 'BatchButton': self.batchButton, 'BatchUpdateField': self.batchUpdateField, 'StoreViewSettings': self.storeViewSettings, } if res_id: if isinstance(res_id, int): res_id = [res_id] self.screen.load(res_id) else: if len(view_type) and view_type[0] == 'form': self.screen.new() self.updateSwitchView() self.reloadTimer = QTimer(self) self.reloadTimer.timeout.connect(self.autoReload) self.pendingReload = False # We always use the Subscriber as the class itself will handle # whether the module exists on the server or not self.subscriber = Rpc.Subscriber(Rpc.session, self) if Settings.value('koo.auto_reload'): self.subscriber.subscribe( 'updated_model:%s' % model, self.autoReload) def isReadonly(self): """ Returns if the form widget is in read only mode :return: True if is in read only mode :rtype: bool """ return self.readonly def save(self): pass def cancel(self): pass def notifyRecordModified(self): self.updateStatus(_('<font color="blue">Document modified</font>')) def setAutoReload(self, value): """ Establishes that every value seconds a reload should be scheduled. If value is < 0 only Subscription based reloads are executed. Note that if value is != 0 Subscription based reloads are always used if available. :param value: :return: None :rtype: None """ if value: # We use both, timer and subscriber as in some cases information # may change only virtually: Say change the color of a row # depending on current time. # If the value is negative we don't start the timer but keep # subscription, so this allows setting -1 in autorefresh when you # don't want timed updates but only when data is changed in the # server. if value > 0: self.reloadTimer.start(int(value) * 1000) if not Settings.value('koo.auto_reload'): # Do not subscribe again if that was already done in the # constructor self.subscriber.subscribe( 'updated_model:%s' % self.model, self.autoReload) else: self.reloadTimer.stop() def setAllowOpenInNewWindow(self, value): self._allowOpenInNewWindow = value def goto(self): if not self.modifiedSave(): return dialog = GoToIdDialog(self) if dialog.exec_() == QDialog.Rejected: return if not dialog.result in self.group.ids(): QMessageBox.information(self, _('Go To Id'), _( "Resouce with ID '%s' not found.") % dialog.result) return self.screen.load([dialog.result]) def setStatusBarVisible(self, value): self.uiStatusLabel.setVisible(value) self.uiStatus.setVisible(value) self.uiRecordStatus.setVisible(value) def showAttachments(self): id = self.screen.currentId() if id: if Settings.value('koo.attachments_dialog'): QApplication.setOverrideCursor(Qt.WaitCursor) try: window = AttachmentDialog(self.model, id, self) window.destroyed.connect(self.attachmentsClosed) except Rpc.RpcException: QApplication.restoreOverrideCursor() return QApplication.restoreOverrideCursor() window.show() else: context = self.context.copy() context.update(Rpc.session.context) action = Rpc.session.execute( '/object', 'execute', 'ir.attachment', 'action_get', context) action['domain'] = [ ('res_model', '=', self.model), ('res_id', '=', id)] context['default_res_model'] = self.model context['default_res_id'] = id Api.instance.executeAction(action, {}, context) else: self.updateStatus(_('No resource selected !')) def attachmentsClosed(self): self.updateStatus() def switchToForm(self): if 'form' in self.viewTypes: self.switchView('form') else: self.switchView() def switchView(self, viewType=None): selectedIds = self.screen.selectedIds() if not self.modifiedSave(): return QApplication.setOverrideCursor(Qt.WaitCursor) try: if (self._allowOpenInNewWindow and QApplication.keyboardModifiers() & Qt.ControlModifier) == Qt.ControlModifier: if QApplication.keyboardModifiers() & Qt.ShiftModifier: target = 'background' else: target = 'current' for ident in selectedIds: Api.instance.createWindow( None, self.model, [ident], view_type='form', mode='form,tree', target=target) else: sender = self.sender() name = str(sender.objectName()) if isinstance(sender, QAction) and name != 'actionSwitch': self.sender().setChecked(True) self.screen.switchView(name) else: self.screen.switchView(viewType) if self.pendingReload: self.reload() self.updateSwitchView() except Rpc.RpcException: pass QApplication.restoreOverrideCursor() def showLogs(self): id = self.screen.currentId() if not id: self.updateStatus(_('You have to select one resource!')) return False res = Rpc.session.execute( '/object', 'execute', self.model, 'perm_read', [id]) message = '' for line in res: todo = [ ('id', _('ID')), ('create_uid', _('Creation User')), ('create_date', _('Creation Date')), ('write_uid', _('Latest Modification by')), ('write_date', _('Latest Modification Date')), ] for (key, val) in todo: if line[key] and key in ('create_uid', 'write_uid') and isinstance(line[key], tuple): line[key] = line[key][1] message += val + ': ' + str(line[key] or '-') + '\n' QMessageBox.information(self, _('Record log'), message) def remove(self): value = QMessageBox.question( self, _('Question'), _('Are you sure you want to remove these records?'), QMessageBox.Yes | QMessageBox.No) if value == 0: QApplication.setOverrideCursor(Qt.WaitCursor) try: if not self.screen.remove(unlink=True): self.updateStatus(_('Resource not removed !')) else: self.updateStatus(_('Resource removed.')) except Rpc.RpcException: pass QApplication.restoreOverrideCursor() def import_(self): dialog = ImportDialog(self) dialog.setModel(self.model) dialog.setup(self.viewTypes, self.viewIds) dialog.exec_() if not self.screen.isModified(): self.reload() def export(self): dialog = ExportDialog(self) dialog.setModel(self.model) dialog.setIds(self.screen.selectedIds()) dialog.setup(self.viewTypes, self.viewIds) dialog.exec_() def new(self): if not self.modifiedSave(): return self.screen.new() def duplicate(self): # Store selected ids before executing modifiedSave() because, there, # the selection is lost. selectedIds = self.screen.selectedIds() if not self.modifiedSave(): return QApplication.setOverrideCursor(Qt.WaitCursor) # Duplicate all selected records but, remember the ID of the copy of the # currently selected record newId = None newIds = [] currentId = self.screen.currentId() for ident in selectedIds: copyId = Rpc.session.execute( '/object', 'execute', self.model, 'copy', ident, {}, Rpc.session.context) newIds.append(copyId) if ident == currentId: newId = copyId # Ensure the copy of the currently selected ID is the first of the list # so it will be the current one, once loaded by screen. if newId in newIds: newIds.remove(newId) newIds.insert(0, newId) self.screen.load(newIds, self.screen.addOnTop()) self.updateStatus( _('<font color="orange">Working now on the duplicated document</font>')) QApplication.restoreOverrideCursor() def save(self): """ Save action :return: :rtype: None or boolean """ if not self.screen.currentRecord(): return QApplication.setOverrideCursor(Qt.WaitCursor) try: modification = self.screen.currentRecord().id ident = self.screen.save() if ident: self.updateStatus( _('<font color="green">Document saved</font>')) if not modification and Settings.value('koo.auto_new'): self.screen.new() QApplication.restoreOverrideCursor() # This condition is an ugly patch to avoid popup error msg, the real # fix must be find why the Koo tries to create the record on the # ERP-Side elif not self.screen.currentRecord().invalidFields: QApplication.restoreOverrideCursor() ident = False else: self.updateStatus(_('<font color="red">Invalid form</font>')) QApplication.restoreOverrideCursor() record = self.screen.currentRecord() fields = [] for field in record.invalidFields: attrs = record.fields()[field].attrs if 'string' in attrs: name = attrs['string'] else: name = field fields.append('<li>{}</li>'.format(name)) fields.sort() fields = '<ul>{}</ul>'.format(''.join(fields)) mesage = _('<p>The following fields have an invalid value and have been highlighted in red:</p>{}<p>Please fix them before saving.</p>') QMessageBox.question( self, _('Error'), mesage.format(fields), QMessageBox.Ok) except Rpc.RpcException: QApplication.restoreOverrideCursor() ident = False return bool(ident) def previous(self): if not self.modifiedSave(): return QApplication.setOverrideCursor(Qt.WaitCursor) try: self.screen.displayPrevious() self.updateStatus() except Rpc.RpcException: pass QApplication.restoreOverrideCursor() def __next__(self): if not self.modifiedSave(): return QApplication.setOverrideCursor(Qt.WaitCursor) try: self.screen.displayNext() self.updateStatus() except Rpc.RpcException as e: pass QApplication.restoreOverrideCursor() def autoReload(self): # Do not reload automatically if it's an editable list # By explicitly disallowing this it makes the global # koo module auto_reload option to be usable. if self.screen.currentView().showsMultipleRecords() and \ not self.screen.currentView().isReadOnly(): return # Do not reload automatically if there are any modified records # However, we take note that there's a pending reload which # will be done in the next switchView() if self.screen.isModified(): self.pendingReload = True return self.reload() # If current view only shows one record self.screen.reload() # only reloads the current record. As we want to be sure that # any new records will appear in the list view (or any view that # shows multiple records), we set pendingReload to be run # when switching view. if not self.screen.currentView().showsMultipleRecords(): self.pendingReload = True def reload(self): if not self.modifiedSave(): return QApplication.setOverrideCursor(Qt.WaitCursor) try: # Ensure attachments are updated by initializing previousId self.previousId = False self.screen.reload() self.updateStatus() self.pendingReload = False except Rpc.RpcException: pass QApplication.restoreOverrideCursor() def cancel(self): QApplication.setOverrideCursor(Qt.WaitCursor) try: self.screen.cancel() self.updateStatus() except Rpc.RpcException: pass QApplication.restoreOverrideCursor() def search(self): if not self.modifiedSave(): return dom = self.domain dialog = SearchDialog(self.model, domain=self.domain, context=self.context, parent=self) if dialog.exec_() == QDialog.Rejected: return self.screen.clear() self.screen.load(dialog.result) def updateStatus(self, message=''): """ Updates the status message of the form (bottom right) :param message: Message :return: None :rtype: None """ if self.model and \ self.screen.currentRecord() and \ self.screen.currentRecord().id: # We don't need to query the server for the number of attachments # if current record has not changed since list update. ident = self.screen.currentRecord().id if ident != self.previousId: ids = Rpc.session.execute( '/object', 'execute', 'ir.attachment', 'search', [('res_model', '=', self.model), ('res_id', '=', ident)] ) self.previousAttachments = ids self.previousId = ident else: ids = self.previousAttachments else: ids = [] self.previousId = False self.previousAttachments = ids message = (_("({} attachments) ").format(len(ids))) + str(message) self.uiStatus.setText(message) def updateSwitchView(self): for action in self._viewActionGroup.actions(): if action.objectName() == self.screen.currentView().viewType(): action.setChecked(True) break def updateRecordStatus(self, position, count, value): if not count: msg = _('No records') else: pos = '_' if position >= 0: pos = str(position + 1) if value is None: # Value will be None only when it's called by the constructor edit = _('No document selected') else: # Other times it'll either be 0 (new document) or the appropiate # object i edit = _('New document') if value > 0: edit = _('Editing document (id: %s)') % str(value) msg = _('Record: %(name)s / %(count)s - %(name2)s') % { 'name': pos, 'count': str(count), 'name2': edit} self.uiRecordStatus.setText(msg) def modifiedSave(self): if self.screen.isModified(): record = self.screen.currentRecord() fields = [] for field in record.modifiedFields(): attrs = record.fields()[field].attrs if 'string' in attrs: name = attrs['string'] else: name = field fields.append('<li>%s</li>' % name) fields.sort() fields = '<ul>%s</ul>' % ''.join(fields) value = QMessageBox.question( self, _('Question'), _('<p>You have modified the following fields in current record:</p>%s<p>Do you want to save the changes?</p>') % fields, QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) if value == 0: return self.save() elif value == 1: self.cancel() return True else: return False else: # If a new record was created but not modified, isModified() # will return False but we want to cancel new records anyway. # We call screen.cancel() directly as we don't want to trigger # an updateStatus() which will result in a server request. self.screen.cancel() return True def batchInsert(self): dialog = BatchInsertDialog(self) dialog.setModel(self.model) dialog.setContext(self.context) dialog.setViewTypes(self.viewTypes) dialog.setViewIds(self.viewIds) dialog.setup() if dialog.exec_() == QDialog.Rejected: return self.reload() def batchUpdate(self): if not self.screen.selectedIds(): QMessageBox.information( self, _('No records selected'), _('No records selected')) return dialog = BatchUpdateDialog(self) dialog.setIds(self.screen.selectedIds()) dialog.setModel(self.model) dialog.setContext(self.context) dialog.setup(self.viewTypes, self.viewIds) if dialog.exec_() == QDialog.Rejected: return self.reload() def batchUpdateField(self): dialog = BatchInsertDialog(self) dialog.setModel(self.model) dialog.setContext(self.context) dialog.setUpdateOnServer(False) dialog.setViewTypes(self.viewTypes) dialog.setViewIds(self.viewIds) dialog.setup() if dialog.exec_() == QDialog.Rejected: return if len(dialog.newValues) != len(self.screen.selectedRecords()): QMessageBox.warning( self, _('Batch Field Update'), _('The number of selected records (%(records)d) does not match the number of records to be inserted in fields (%(fields)d).') % { 'records': len(dialog.newValues), 'fields': len(self.screen.selectedRecords()) }) return i = 0 for record in self.screen.selectedRecords(): record.setValue(dialog.newField, dialog.newValues[i]) i += 1 self.save() self.reload() def storeViewSettings(self): self.screen.storeViewSettings() def closeWidget(self): self.screen.storeViewSettings() self.reloadTimer.stop() self.subscriber.unsubscribe() self.closed.emit() def canClose(self, urgent=False): if self.modifiedSave(): # Here suppose that if we return True the form/tab will # actually be closed, so stop reload timer so it doesn't # remain active if the object is freed. self.reloadTimer.stop() self.subscriber.unsubscribe() # Store settings of all opened views before closing the tab. self.screen.storeViewSettings() return True else: return False def actions(self): return self.screen.actions def switchViewMenu(self): return self._switchViewMenu def help(self, button): if not isHelpWidgetAvailable: return QApplication.setOverrideCursor(Qt.WaitCursor) helpWidget = Help.HelpWidget(button) helpWidget.setLabel(self.name) helpWidget.setType(helpWidget.ViewType) helpWidget.setFilter( (self.model, self.screen.currentView().viewType())) helpWidget.show() QApplication.restoreOverrideCursor() def __del__(self): self.group.__del__() del self.group def batchButton(self): viewTypes = self.viewTypes viewIds = self.viewIds group = RecordGroup(self.model, context=self.context) group.setDomainForEmptyGroup() group.load(self.screen.selectedIds()) screen = Screen(self) screen.setRecordGroup(self.group) screen.setEmbedded(True) if 'form' in viewTypes: queue = ViewQueue() queue.setup(viewTypes, viewIds) type = '' while type != 'form': ident, type = next(queue) screen.setupViews(['form'], [ident]) else: screen.setupViews(['form'], [False]) from Koo.Fields.Button import ButtonFieldWidget from Koo.Common import Common buttons = {} for key, widget in screen.currentView().widgets.items(): if isinstance(widget, ButtonFieldWidget): buttons[str(widget.button.text())] = widget.name selectionDialog = Common.SelectionDialog( _('Choose action to apply to selected records'), buttons, self) if selectionDialog.exec_() == QDialog.Rejected: return buttonString = selectionDialog.result[0] buttonName = selectionDialog.result[1] if QMessageBox.question( self, _("Batch Update"), _("Do you really want to push button '%s' of all selected records?") % buttonString, QMessageBox.Yes|QMessageBox.No ) == 1: return for ident in self.screen.selectedIds(): screen.display(ident) screen.currentView().widgets[buttonName].executeButton(screen, ident) self.reload()
class FormWidget( QWidget, FormWidgetUi ): # form constructor: # model -> Name of the model the form should handle # res_id -> List of ids of type 'model' to load # domain -> Domain the models should be in # view_type -> type of view: form, tree, graph, calendar, ... # view_ids -> Id's of the views 'ir.ui.view' to show # context -> Context for the current data set # parent -> Parent widget of the form # name -> User visible title of the form def __init__(self, model, res_id=False, domain=None, view_type=None, view_ids=None, context=None, parent=None, name=False): QWidget.__init__(self,parent) FormWidgetUi.__init__(self) self.setupUi( self ) if domain is None: domain = [] if view_ids is None: view_ids = [] if context is None: context = {} # This variable holds the id used to update status (and show number of attachments) # If it doesn't change we won't update the number of attachments, avoiding some server # calls. self.previousId = False self.previousAttachments = False # Workaround: In some cases (detected in some CRM actions) view_type and view_ids # may contain duplicate entries. Here we remove duplicates (ensuring lists order is kept). if view_type: new_view_ids = [] new_view_type = [] for i in xrange(len(view_type)): if not view_type[i] in new_view_type: if i < len(view_ids): new_view_ids.append( view_ids[i] ) new_view_type.append( view_type[i] ) view_ids = new_view_ids view_type = new_view_type if not view_type: view_type = ['form','tree'] else: if view_type[0] in ['graph'] and not res_id: res_id = Rpc.session.execute('/object', 'execute', model, 'search', domain) fields = {} self.model = model self.previousAction = None self.fields = fields self.domain = domain self.context = context self.viewTypes = view_type self.viewIds = view_ids self.devel_mode = Settings.value("client.devel_mode", False) self._switchViewMenu = QMenu( self ) self._viewActionGroup = QActionGroup( self ) self._viewActionGroup.setExclusive( True ) for view in self.viewTypes: action = ViewFactory.viewAction( view, self ) if not action: continue self.connect( action, SIGNAL('triggered()'), self.switchView ) self._switchViewMenu.addAction( action ) self._viewActionGroup.addAction( action ) self.group = RecordGroup( self.model, context=self.context ) if Settings.value('koo.sort_mode') == 'visible_items': self.group.setSortMode( RecordGroup.SortVisibleItems ) self.group.setDomain( domain ) self.connect(self.group, SIGNAL('modified'), self.notifyRecordModified) self.screen.setRecordGroup( self.group ) self.screen.setEmbedded( False ) self.connect( self.screen, SIGNAL('activated()'), self.switchToForm ) self.connect( self.screen, SIGNAL('currentChanged()'), self.updateStatus ) self.connect( self.screen, SIGNAL('closed()'), self.closeWidget ) self.connect( self.screen, SIGNAL('recordMessage(int,int,int)'), self.updateRecordStatus ) self.connect( self.screen, SIGNAL('statusMessage(QString)'), self.updateStatus ) self._allowOpenInNewWindow = True # Remove ids with False value self.screen.setupViews( view_type, view_ids ) if name: self.name = name else: self.name = self.screen.currentView().title self.handlers = { 'New': self.new, 'Save': self.save, 'Export': self.export, 'Import': self.import_, 'Delete': self.remove, 'Find': self.search, 'Previous': self.previous, 'Next': self.next, 'GoToResourceId': self.goto, 'AccessLog': self.showLogs, 'Reload': self.reload, 'Switch': self.switchView, 'Attach': self.showAttachments, 'Duplicate': self.duplicate, 'BatchInsert': self.batchInsert, 'BatchUpdate': self.batchUpdate, 'BatchButton': self.batchButton, 'BatchUpdateField': self.batchUpdateField, 'StoreViewSettings': self.storeViewSettings, } if res_id: if isinstance(res_id, int): res_id = [res_id] self.screen.load(res_id) else: if len(view_type) and view_type[0]=='form': self.screen.new() self.updateSwitchView() self.reloadTimer = QTimer(self) self.connect( self.reloadTimer, SIGNAL('timeout()'), self.autoReload ) self.pendingReload = False # We always use the Subscriber as the class itself will handle # whether the module exists on the server or not self.subscriber = Rpc.Subscriber(Rpc.session, self) if Settings.value('koo.auto_reload'): self.subscriber.subscribe( 'updated_model:%s' % model, self.autoReload ) def notifyRecordModified(self): self.updateStatus( _('<font color="blue">Document modified</font>') ) ## @brief Establishes that every value seconds a reload should be scheduled. # If value is < 0 only Subscription based reloads are executed. Note that if # value is != 0 Subscription based reloads are always used if available. def setAutoReload(self, value): if value: # We use both, timer and subscriber as in some cases information may change # only virtually: Say change the color of a row depending on current time. # If the value is negative we don't start the timer but keep subscription, # so this allows setting -1 in autorefresh when you don't want timed updates # but only when data is changed in the server. if value > 0: self.reloadTimer.start( int(value) * 1000 ) if not Settings.value('koo.auto_reload'): # Do not subscribe again if that was already done in the constructor self.subscriber.subscribe( 'updated_model:%s' % self.model, self.autoReload ) else: self.reloadTimer.stop() def setAllowOpenInNewWindow( self, value ): self._allowOpenInNewWindow = value def goto(self): if not self.modifiedSave(): return dialog = GoToIdDialog( self ) if dialog.exec_() == QDialog.Rejected: return if not dialog.result in self.group.ids(): QMessageBox.information(self, _('Go To Id'), _("Resouce with ID '%s' not found.") % dialog.result ) return self.screen.load( [dialog.result] ) def setStatusBarVisible(self, value): self.uiStatusLabel.setVisible( value ) self.uiStatus.setVisible( value ) self.uiRecordStatus.setVisible( value ) def showAttachments(self): id = self.screen.currentId() if id: if Settings.value('koo.attachments_dialog'): QApplication.setOverrideCursor( Qt.WaitCursor ) try: window = AttachmentDialog(self.model, id, self) self.connect( window, SIGNAL('destroyed()'), self.attachmentsClosed ) except Rpc.RpcException, e: QApplication.restoreOverrideCursor() return QApplication.restoreOverrideCursor() window.show() else: context = self.context.copy() context.update(Rpc.session.context) action = Rpc.session.execute('/object', 'execute', 'ir.attachment', 'action_get', context) action['domain'] = [('res_model', '=', self.model), ('res_id', '=', id)] context['default_res_model'] = self.model context['default_res_id'] = id Api.instance.executeAction( action, {}, context ) else: