def __init__(self, admin=None, parent=None, editable=True, field_name='manytoone', actions=[ field_action.ClearObject(), field_action.SelectObject(), field_action.NewObject(), field_action.OpenObject() ], **kwargs): """ :param entity_admin : The Admin interface for the object on the one side of the relation """ CustomEditor.__init__(self, parent) self.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Fixed) self.setObjectName(field_name) self.admin = admin self.new_value = None self._entity_representation = '' self.obj = None self._last_highlighted_entity_getter = None self.layout = QtWidgets.QHBoxLayout() self.layout.setSpacing(0) self.layout.setContentsMargins(0, 0, 0, 0) # Search input self.search_input = DecoratedLineEdit(self) self.search_input.setPlaceholderText(_('Search...')) self.search_input.textEdited.connect(self.textEdited) self.search_input.set_minimum_width(20) self.search_input.arrow_down_key_pressed.connect( self.on_arrow_down_key_pressed) # suppose garbage was entered, we need to refresh the content self.search_input.editingFinished.connect( self.search_input_editing_finished) self.setFocusProxy(self.search_input) # Search Completer self.completer = QtGui.QCompleter() self.completions_model = self.CompletionsModel(self.completer) self.completer.setModel(self.completions_model) self.completer.setCaseSensitivity(Qt.CaseInsensitive) self.completer.setCompletionMode( QtGui.QCompleter.UnfilteredPopupCompletion) self.completer.activated[QtCore.QModelIndex].connect( self.completionActivated) self.completer.highlighted[QtCore.QModelIndex].connect( self.completion_highlighted) self.search_input.setCompleter(self.completer) # Setup layout self.layout.addWidget(self.search_input) self.setLayout(self.layout) self.add_actions(actions, self.layout) get_signal_handler().connect_signals(self)
def __init__(self, session, update_depending_objects=True): # # @todo : deleting of objects should be moved from the collection_proxy # to here, once deleting rows is reimplemented as an action # # @todo : handle the creation of new objects # signal_handler = get_signal_handler() # list of objects that need to receive an update signal dirty_objects = set(session.dirty) #for dirty_object in session.dirty: # obj_admin = admin.get_related_admin( type( dirty_object ) ) # if obj_admin: # dirty_objects.update( obj_admin.get_depending_objects( dirty_object ) ) for obj_to_delete in session.deleted: # obj_admin = admin.get_related_admin( type( obj_to_delete ) ) # if obj_admin: # dirty_objects.update( obj_admin.get_depending_objects( obj_to_delete ) ) signal_handler.sendEntityDelete(self, obj_to_delete) # # Only now is the full list of dirty objects available, so the deleted # can be removed from them # for obj_to_delete in session.deleted: try: dirty_objects.remove(obj_to_delete) except KeyError: pass session.flush() for obj in dirty_objects: signal_handler.sendEntityUpdate(self, obj)
def __init__( self, session, update_depending_objects = True ): # # @todo : deleting of objects should be moved from the collection_proxy # to here, once deleting rows is reimplemented as an action # # @todo : handle the creation of new objects # signal_handler = get_signal_handler() # list of objects that need to receive an update signal dirty_objects = set( session.dirty ) #for dirty_object in session.dirty: # obj_admin = admin.get_related_admin( type( dirty_object ) ) # if obj_admin: # dirty_objects.update( obj_admin.get_depending_objects( dirty_object ) ) for obj_to_delete in session.deleted: # obj_admin = admin.get_related_admin( type( obj_to_delete ) ) # if obj_admin: # dirty_objects.update( obj_admin.get_depending_objects( obj_to_delete ) ) signal_handler.sendEntityDelete( self, obj_to_delete ) # # Only now is the full list of dirty objects available, so the deleted # can be removed from them # for obj_to_delete in session.deleted: try: dirty_objects.remove( obj_to_delete ) except KeyError: pass session.flush() for obj in dirty_objects: signal_handler.sendEntityUpdate( self, obj )
def request(): from sqlalchemy.orm.session import Session from camelot.view.remote_signals import get_signal_handler o = entity_getter() self._model_function(o) if self._flush: sh = get_signal_handler() Session.object_session(o).flush([o]) sh.sendEntityUpdate(self, o) return True
def refresh_objects(): from camelot.view.remote_signals import get_signal_handler signal_handler = get_signal_handler() refreshed_objects = [] for _key, value in session.identity_map.items(): session.refresh(value) refreshed_objects.append(value) for o in refreshed_objects: signal_handler.sendEntityUpdate(None, o) return refreshed_objects
def refresh_objects(): from camelot.view.remote_signals import get_signal_handler signal_handler = get_signal_handler() refreshed_objects = [] for _key, value in session.identity_map.items(): session.refresh(value) refreshed_objects.append(value) for o in refreshed_objects: signal_handler.sendEntityUpdate(None, o) return refreshed_objects
def run(self): from sqlalchemy.orm.session import Session from camelot.view.remote_signals import get_signal_handler signal_handler = get_signal_handler() collection = list(self._collection_getter()) self.update_maximum_signal.emit( len(collection) ) for i, entity in enumerate(collection): message = self.update_entity(entity) Session.object_session( entity ).flush( [entity] ) signal_handler.sendEntityUpdate( self, entity ) self.update_progress_signal.emit( i, message or '')
def request(): from sqlalchemy.orm.session import Session from camelot.view.remote_signals import get_signal_handler o = entity_getter() self._model_function(o) if self._flush: sh = get_signal_handler() Session.object_session(o).flush([o]) sh.sendEntityUpdate(self, o) return True
def run(self): from sqlalchemy.orm.session import Session from camelot.view.remote_signals import get_signal_handler signal_handler = get_signal_handler() collection = list(self._collection_getter()) self.update_maximum_signal.emit(len(collection)) for i, entity in enumerate(collection): message = self.update_entity(entity) Session.object_session(entity).flush([entity]) signal_handler.sendEntityUpdate(self, entity) self.update_progress_signal.emit(i, message or '')
def request(): from sqlalchemy.orm.session import Session from camelot.view.remote_signals import get_signal_handler sh = get_signal_handler() c = list(collection_getter()) s = list(selection_getter()) self._model_function(c, s, options) to_flush = [] if self._selection_flush: to_flush = s if self._collection_flush: to_flush = c for o in to_flush: Session.object_session(o).flush([o]) sh.sendEntityUpdate(self, o)
def request(): from sqlalchemy.orm.session import Session from camelot.view.remote_signals import get_signal_handler sh = get_signal_handler() c = list(collection_getter()) s = list(selection_getter()) self._model_function( c, s, options ) to_flush = [] if self._selection_flush: to_flush = s if self._collection_flush: to_flush = c for o in to_flush: Session.object_session( o ).flush( [o] ) sh.sendEntityUpdate( self, o )
def model_run( self, model_context ): import sqlalchemy.exc as sa_exc from camelot.core.orm import Session from camelot.view import action_steps from camelot.view.remote_signals import get_signal_handler LOGGER.debug('session refresh requested') progress_db_message = ugettext('Reload data from database') progress_view_message = ugettext('Update screens') session = Session() signal_handler = get_signal_handler() refreshed_objects = [] expunged_objects = [] # # Loop over the objects one by one to be able to detect the deleted # objects # session_items = len( session.identity_map ) for i, (_key, obj) in enumerate( session.identity_map.items() ): try: session.refresh( obj ) refreshed_objects.append( obj ) except sa_exc.InvalidRequestError: # # this object could not be refreshed, it was probably deleted # outside the scope of this session, so assume it is deleted # from the application its point of view # session.expunge( obj ) expunged_objects.append( obj ) if i%10 == 0: yield action_steps.UpdateProgress( i, session_items, progress_db_message ) yield action_steps.UpdateProgress( text = progress_view_message ) for obj in refreshed_objects: signal_handler.sendEntityUpdate( None, obj ) for obj in expunged_objects: signal_handler.sendEntityDelete( None, obj ) yield action_steps.Refresh()
def model_run(self, model_context): import sqlalchemy.exc as sa_exc from camelot.core.orm import Session from camelot.view import action_steps from camelot.view.remote_signals import get_signal_handler LOGGER.debug('session refresh requested') progress_db_message = ugettext('Reload data from database') progress_view_message = ugettext('Update screens') session = Session() signal_handler = get_signal_handler() refreshed_objects = [] expunged_objects = [] # # Loop over the objects one by one to be able to detect the deleted # objects # session_items = len(session.identity_map) for i, (_key, obj) in enumerate(six.iteritems(session.identity_map)): try: session.refresh(obj) refreshed_objects.append(obj) except sa_exc.InvalidRequestError: # # this object could not be refreshed, it was probably deleted # outside the scope of this session, so assume it is deleted # from the application its point of view # session.expunge(obj) expunged_objects.append(obj) if i % 10 == 0: yield action_steps.UpdateProgress(i, session_items, progress_db_message) yield action_steps.UpdateProgress(text=progress_view_message) for obj in refreshed_objects: signal_handler.sendEntityUpdate(self, obj) for obj in expunged_objects: signal_handler.sendEntityDelete(self, obj) yield action_steps.Refresh()
def __init__(self, app_admin, entity): """ :param app_admin: the application admin object for this application, if None, then the default application_admin is taken :param entity: the entity class for which this admin instance is to be used """ from camelot.view.remote_signals import get_signal_handler if not app_admin: from camelot.admin.application_admin import get_application_admin self.app_admin = get_application_admin() else: self.app_admin = app_admin self.rsh = get_signal_handler() if entity: from camelot.view.model_thread import get_model_thread self.entity = entity self.mt = get_model_thread() # # caches to prevent recalculation of things # self._field_attributes = dict() self._subclasses = None
def __init__(self, app_admin, entity): """ :param app_admin: the application admin object for this application, if None, then the default application_admin is taken :param entity: the entity class for which this admin instance is to be used """ from camelot.view.remote_signals import get_signal_handler if not app_admin: from camelot.admin.application_admin import get_application_admin self.app_admin = get_application_admin() else: self.app_admin = app_admin self.rsh = get_signal_handler() if entity: from camelot.view.model_thread import get_model_thread self.entity = entity self.mt = get_model_thread() # # caches to prevent recalculation of things # self._field_attributes = dict() self._subclasses = None
def __init__( self, obj ): signal_handler = get_signal_handler() if obj != None: signal_handler.sendEntityUpdate( self, obj )
def __init__(self, obj): self.obj = obj signal_handler = get_signal_handler() if self.obj != None: signal_handler.sendEntityDelete(self, self.obj)
def __init__( self, admin, max_number_of_rows=10, flush_changes=True, cache_collection_proxy=None, ): """ :param admin: the admin interface for the items in the collection :param cache_collection_proxy: the CollectionProxy on which this CollectionProxy will reuse the cache. Passing a cache has the advantage that objects that were present in the original cache will remain at the same row in the new cache This is used when a form is created from a tableview. Because between the last query of the tableview, and the first of the form, the object might have changed position in the query. """ super(CollectionProxy, self).__init__() assert object_thread(self) from camelot.view.model_thread import get_model_thread # # The source model will contain the actual data stripped from the # objects in the collection. # self.source_model = QtGui.QStandardItemModel() self.setSourceModel(self.source_model) self.logger = logging.getLogger(logger.name + '.%s' % id(self)) self.logger.debug('initialize query table for %s' % (admin.get_verbose_name())) # the mutex is recursive to avoid blocking during unittest, when # model and view are used in the same thread self._mutex = QtCore.QMutex(QtCore.QMutex.Recursive) self.admin = admin self.list_action = admin.list_action self.row_model_context = RowModelContext() self.row_model_context.admin = admin self.settings = self.admin.get_settings() self._horizontal_header_height = QtGui.QFontMetrics( self._header_font_required).height() + 10 self._header_font_metrics = QtGui.QFontMetrics(self._header_font) vertical_header_font_height = QtGui.QFontMetrics( self._header_font).height() self._vertical_header_height = vertical_header_font_height * self.admin.lines_per_row + 10 self.vertical_header_size = QtCore.QSize(16 + 10, self._vertical_header_height) self.validator = admin.get_validator(self) self._collection = [] self.flush_changes = flush_changes self.mt = get_model_thread() # Set database connection and load data self._rows = None self._columns = [] self._static_field_attributes = [] self._max_number_of_rows = max_number_of_rows max_cache = 10 * self.max_number_of_rows if cache_collection_proxy: cached_entries = len(cache_collection_proxy.display_cache) max_cache = max(cached_entries, max_cache) self.display_cache = cache_collection_proxy.display_cache.shallow_copy( max_cache) self.edit_cache = cache_collection_proxy.edit_cache.shallow_copy( max_cache) self.attributes_cache = cache_collection_proxy.attributes_cache.shallow_copy( max_cache) self.action_state_cache = cache_collection_proxy.action_state_cache.shallow_copy( max_cache) else: self.display_cache = Fifo(max_cache) self.edit_cache = Fifo(max_cache) self.attributes_cache = Fifo(max_cache) self.action_state_cache = Fifo(max_cache) # The rows in the table for which a cache refill is under request self.rows_under_request = set() self._update_requests = list() self._rowcount_requests = list() # The rows that have unflushed changes self.unflushed_rows = set() self._sort_and_filter = SortingRowMapper() self.row_changed_signal.connect(self._emit_changes) self._rows_about_to_be_inserted_signal.connect( self._rows_about_to_be_inserted, Qt.QueuedConnection) self._rows_inserted_signal.connect(self._rows_inserted, Qt.QueuedConnection) self.rsh = get_signal_handler() self.rsh.connect_signals(self) # # the initial collection might contain unflushed rows post(self._update_unflushed_rows) # # in that way the number of rows is requested as well if cache_collection_proxy: self.setRowCount(cache_collection_proxy.rowCount()) self.logger.debug('initialization finished')
def __init__(self, obj): signal_handler = get_signal_handler() if obj != None: signal_handler.sendEntityCreate(self, obj)
def __init__( self, admin, collection_getter, columns_getter, max_number_of_rows = 10, edits = None, flush_changes = True, cache_collection_proxy = None ): """ :param admin: the admin interface for the items in the collection :param collection_getter: a function that takes no arguments and returns the collection that will be visualized. This function will be called inside the model thread, to prevent delays when this function causes the database to be hit. If the collection is a list, it should not contain any duplicate elements. :param columns_getter: a function that takes no arguments and returns the columns that will be cached in the proxy. This function will be called inside the model thread. :param cache_collection_proxy: the CollectionProxy on which this CollectionProxy will reuse the cache. Passing a cache has the advantage that objects that were present in the original cache will remain at the same row in the new cache This is used when a form is created from a tableview. Because between the last query of the tableview, and the first of the form, the object might have changed position in the query. """ super(CollectionProxy, self).__init__() from camelot.view.model_thread import get_model_thread self.logger = logging.getLogger(logger.name + '.%s'%id(self)) self.logger.debug('initialize query table for %s' % (admin.get_verbose_name())) self._mutex = QtCore.QMutex() self.admin = admin self._horizontal_header_height = QtGui.QFontMetrics( self._header_font_required ).height() + 10 vertical_header_font_height = QtGui.QFontMetrics( self._header_font ).height() self._vertical_header_height = vertical_header_font_height * self.admin.lines_per_row + 10 self.iconSize = QtCore.QSize( vertical_header_font_height, vertical_header_font_height ) if self.header_icon: self.form_icon = QtCore.QVariant( self.header_icon.getQIcon().pixmap( self.iconSize ) ) else: self.form_icon = QtCore.QVariant() self.validator = admin.create_validator( self ) self._collection_getter = collection_getter self.column_count = 0 self.flush_changes = flush_changes self.delegate_manager = None self.mt = get_model_thread() # Set database connection and load data self._rows = 0 self._columns = [] self._static_field_attributes = [] self._max_number_of_rows = max_number_of_rows if cache_collection_proxy: self.display_cache = cache_collection_proxy.display_cache.shallow_copy( 10 * self.max_number_of_rows ) self.edit_cache = cache_collection_proxy.edit_cache.shallow_copy( 10 * self.max_number_of_rows ) self.attributes_cache = cache_collection_proxy.attributes_cache.shallow_copy( 10 * self.max_number_of_rows ) else: self.display_cache = Fifo( 10 * self.max_number_of_rows ) self.edit_cache = Fifo( 10 * self.max_number_of_rows ) self.attributes_cache = Fifo( 10 * self.max_number_of_rows ) # The rows in the table for which a cache refill is under request self.rows_under_request = set() self._update_requests = list() # The rows that have unflushed changes self.unflushed_rows = set() self._sort_and_filter = SortingRowMapper() # Set edits self.edits = edits or [] self.row_changed_signal.connect( self._emit_changes ) self.rsh = get_signal_handler() self.rsh.connect_signals( self ) def get_columns(): self._columns = columns_getter() self._static_field_attributes = list(self.admin.get_static_field_attributes([c[0] for c in self._columns])) return self._columns post( get_columns, self.setColumns ) # # the initial collection might contain unflushed rows post( self.updateUnflushedRows ) # # in that way the number of rows is requested as well if cache_collection_proxy: self.setRowCount( cache_collection_proxy.rowCount() ) else: post( self.getRowCount, self.setRowCount ) self.logger.debug( 'initialization finished' )
def __init__(self, admin, collection_getter, columns_getter, max_number_of_rows=10, edits=None, flush_changes=True, cache_collection_proxy=None): """ :param admin: the admin interface for the items in the collection :param collection_getter: a function that takes no arguments and returns the collection that will be visualized. This function will be called inside the model thread, to prevent delays when this function causes the database to be hit. If the collection is a list, it should not contain any duplicate elements. :param columns_getter: a function that takes no arguments and returns the columns that will be cached in the proxy. This function will be called inside the model thread. :param cache_collection_proxy: the CollectionProxy on which this CollectionProxy will reuse the cache. Passing a cache has the advantage that objects that were present in the original cache will remain at the same row in the new cache This is used when a form is created from a tableview. Because between the last query of the tableview, and the first of the form, the object might have changed position in the query. """ super(CollectionProxy, self).__init__() from camelot.view.model_thread import get_model_thread self.logger = logging.getLogger(logger.name + '.%s' % id(self)) self.logger.debug('initialize query table for %s' % (admin.get_verbose_name())) self._mutex = QtCore.QMutex() self.admin = admin self._horizontal_header_height = QtGui.QFontMetrics( self._header_font_required).height() + 10 vertical_header_font_height = QtGui.QFontMetrics( self._header_font).height() self._vertical_header_height = vertical_header_font_height * self.admin.lines_per_row + 10 self.iconSize = QtCore.QSize(vertical_header_font_height, vertical_header_font_height) if self.header_icon: self.form_icon = QtCore.QVariant( self.header_icon.getQIcon().pixmap(self.iconSize)) else: self.form_icon = QtCore.QVariant() self.validator = admin.create_validator(self) self._collection_getter = collection_getter self.column_count = 0 self.flush_changes = flush_changes self.delegate_manager = None self.mt = get_model_thread() # Set database connection and load data self._rows = 0 self._columns = [] self._static_field_attributes = [] self._max_number_of_rows = max_number_of_rows if cache_collection_proxy: self.display_cache = cache_collection_proxy.display_cache.shallow_copy( 10 * self.max_number_of_rows) self.edit_cache = cache_collection_proxy.edit_cache.shallow_copy( 10 * self.max_number_of_rows) self.attributes_cache = cache_collection_proxy.attributes_cache.shallow_copy( 10 * self.max_number_of_rows) else: self.display_cache = Fifo(10 * self.max_number_of_rows) self.edit_cache = Fifo(10 * self.max_number_of_rows) self.attributes_cache = Fifo(10 * self.max_number_of_rows) # The rows in the table for which a cache refill is under request self.rows_under_request = set() self._update_requests = list() # The rows that have unflushed changes self.unflushed_rows = set() self._sort_and_filter = SortingRowMapper() # Set edits self.edits = edits or [] self.row_changed_signal.connect(self._emit_changes) self.rsh = get_signal_handler() self.rsh.connect_signals(self) def get_columns(): self._columns = columns_getter() self._static_field_attributes = list( self.admin.get_static_field_attributes( [c[0] for c in self._columns])) return self._columns post(get_columns, self.setColumns) # # the initial collection might contain unflushed rows post(self.updateUnflushedRows) # # in that way the number of rows is requested as well if cache_collection_proxy: self.setRowCount(cache_collection_proxy.rowCount()) else: post(self.getRowCount, self.setRowCount) self.logger.debug('initialization finished')