Ejemplo n.º 1
0
    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)
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
 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 )
Ejemplo n.º 4
0
 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
Ejemplo n.º 5
0
 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
Ejemplo n.º 6
0
 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
Ejemplo n.º 7
0
 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 '')
Ejemplo n.º 8
0
            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 '')
Ejemplo n.º 10
0
 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)
Ejemplo n.º 11
0
 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 )
Ejemplo n.º 12
0
 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()
Ejemplo n.º 13
0
 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()
Ejemplo n.º 14
0
    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
Ejemplo n.º 15
0
    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
Ejemplo n.º 16
0
 def __init__( self, obj ):
     signal_handler = get_signal_handler()
     if obj != None:
         signal_handler.sendEntityUpdate( self, obj )
Ejemplo n.º 17
0
 def __init__(self, obj):
     self.obj = obj
     signal_handler = get_signal_handler()
     if self.obj != None:
         signal_handler.sendEntityDelete(self, self.obj)
Ejemplo n.º 18
0
    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')
Ejemplo n.º 19
0
 def __init__(self, obj):
     signal_handler = get_signal_handler()
     if obj != None:
         signal_handler.sendEntityCreate(self, obj)
Ejemplo n.º 20
0
    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' )
Ejemplo n.º 21
0
    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')