### Navigation

- index
- modules |
- Camelot documentation »
- Module code »

# Source code for camelot.view.filters

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

    """
    Python structures to represent filters.
    These structures can be transformed to QT forms.
    """

    import datetime

    from PyQt4 import QtCore, QtGui
    from sqlalchemy import sql

    from camelot.view.controls.editors import DateEditor
    from camelot.view.model_thread import gui_function
    from camelot.core.utils import ugettext_lazy as _

    def structure_to_filter(structure):
        """Convert a python data structure to a filter, using the following rules :

        if structure is an instance of Filter, return structure
        else create a GroupBoxFilter from the structure
        """
        if isinstance(structure, Filter):
            return structure
        return GroupBoxFilter(structure)

    class Filter(object):
        """Base class for filters"""

        class All(object):
            pass

        def __init__(self, attribute, default=All):
            """
            :param attribute: the attribute on which to filter, this attribute
                may contain dots to indicate relationships that need to be followed,
                eg.  'person.name'
            :param default: the default value to filter on when the view opens,
                defaults to showing all records.
            """
            self.attribute = attribute
            self._default = default

        @gui_function
        def render(self, parent, name, options):
            """Render this filter as a qt object
            @param parent: its parent widget
            @param name: the name of the filter
            @param options: the options that can be selected, where each option is a list
            of tuples containting (option_name, query_decorator)

            The name and the list of options can be fetched with get_name_and_options"""
            raise NotImplementedError()

        def get_name_and_options(self, admin):
            """return a tuple of the name of the filter and a list of options that can be selected.
            Each option is a tuple of the name of the option, and a filter function to
            decorate a query
            @return:  (filter_name, [(option_name, query_decorator), ...)
            """
            from sqlalchemy.sql import select
            #from sqlalchemy.sql.expression import alias
            from sqlalchemy import orm
            from elixir import session
            filter_names = []
            joins = []
            #
            # in case of inheritance, use the local table to be able to join,
            # otherwise use the mapped table, to be able to filter on views
            #
            if admin.mapper!=admin.mapper.base_mapper:
                table = admin.mapper.local_table
            else:
                table = admin.mapper.mapped_table
            path = self.attribute.split('.')
            for field_name in path:
                attributes = admin.get_field_attributes(field_name)
                filter_names.append(attributes['name'])
                # @todo: if the filter is not on an attribute of the relation, but on the relation itselves
                if 'target' in attributes:
                    admin = attributes['admin']
                    joins.append(field_name)
                    if attributes['direction'] == orm.interfaces.MANYTOONE:
                        table = admin.entity.table.join( table )
                    else:
                        table = admin.entity.table

            col = getattr( admin.entity, field_name )
            query = select([col], distinct=True, order_by=col.asc()).select_from(table)

            def create_decorator(col, attributes, value, joins):
                def decorator(q):
                    if joins:
                        q = q.join( *joins, aliased=True)
                    if 'precision' in attributes:
                        delta = pow( 10,  -1*attributes['precision'])
                        return q.filter( sql.and_(col < value+delta, col > value-delta) )
                    return q.filter(col==value)

                return decorator

            options = []
            for value in session.execute(query):
                if 'to_string' in attributes:
                    option_name = attributes['to_string'](value[0])
                else:
                    option_name = value[0]

                options.append((_(option_name), create_decorator(col, attributes, value[0], joins)))

            return (filter_names[0],[(_('All'), lambda q: q)] + options)

    class FilterWidget( QtGui.QGroupBox ):
        """A box containing a filter that can be applied on a table view, this filter is
        based on the distinct values in a certain column"""

        filter_changed_signal = QtCore.pyqtSignal()

        def __init__(self, name, choices, parent):
            super( FilterWidget, self ).__init__( unicode( name ), parent )
            layout = QtGui.QHBoxLayout()
            layout.setSpacing( 2 )
            self.setLayout( layout )
            self.group = QtGui.QButtonGroup(self)
            self.item = name
            self.unique_values = []
            self.choices = None
            self.setChoices( choices )

        @QtCore.pyqtSlot(bool)
        def emit_filter_changed(self, state):
            self.filter_changed_signal.emit()

        def setChoices(self, choices):
            self.choices = choices
            layout = self.layout()
            button_layout = QtGui.QVBoxLayout()

            for i, name in enumerate([unicode(c[0]) for c in choices]):
                button = QtGui.QRadioButton(name, self)
                button_layout.addWidget(button)
                self.group.addButton(button, i)
                if i==0:
                    button.setChecked(True)
                button.toggled.connect( self.emit_filter_changed )

            layout.addLayout( button_layout )
            self.setLayout(layout)

        def decorate_query(self, query):
            checked = self.group.checkedId()
            if checked>=0:
                return self.choices[checked][1](query)
            return query

    class GroupBoxFilter(Filter):
        """Filter where the items are displayed in a QGroupBox"""

        @gui_function
        def render(self, parent, name, options):
            return FilterWidget(name, options, parent)

    class GroupBoxFilterWidget( QtGui.QGroupBox ):
        """Flter widget based on a QGroupBox"""

        filter_changed_signal = QtCore.pyqtSignal()

        def __init__(self, name, choices, parent):
            super( GroupBoxFilterWidget, self ).__init__( unicode( name ), parent )
            layout = QtGui.QVBoxLayout()
            layout.setSpacing( 2 )
            self.choices = choices
            combobox = QtGui.QComboBox(self)
            for i,(name,decorator) in enumerate(choices):
                combobox.insertItem(i, unicode(name), QtCore.QVariant(decorator))
            layout.addWidget( combobox )
            self.setLayout(layout)
            self.current_index = 0
            combobox.currentIndexChanged.connect( self.emit_filter_changed )

        @QtCore.pyqtSlot(int)
        def emit_filter_changed(self, index):
            self.current_index = index
            self.filter_changed_signal.emit()

        def decorate_query(self, query):
            if self.current_index>=0:
                return self.choices[self.current_index][1](query)
            return query

    class ComboBoxFilter(Filter):
        """Filter where the items are displayed in a QComboBox"""

        @gui_function
        def render(self, parent, name, options):
            return GroupBoxFilterWidget(name, options, parent)

    class EditorFilter(Filter):
        """Filter that presents the user with an editor, allowing the user to enter
        a value on which to filter, and at the same time to show 'All' or 'None'
        """

        def __init__(self, field_name, verbose_name=None):
            """:param field: the name of the field on which to filter"""
            super(EditorFilter, self).__init__(field_name)
            self._field_name = field_name
            self._verbose_name = verbose_name

        def render(self, parent, name, options):
            from camelot.view.controls.filter_operator import FilterOperator
            entity, field_name, field_attributes = options
            return FilterOperator(entity, field_name, field_attributes, parent)

        def get_name_and_options(self, admin):
            field_attributes = admin.get_field_attributes(self._field_name)
            name = self._verbose_name or field_attributes['name']
            return name, (admin.entity, self._field_name, field_attributes)

    class DateFilterWidget( QtGui.QGroupBox ):
        """Filter widget based on a DateEditor"""

        filter_changed_signal = QtCore.pyqtSignal()

        def __init__(self, name, query_decorator, default, parent):
            super( DateFilterWidget, self ).__init__( unicode( name ), parent )
            layout = QtGui.QVBoxLayout()
            layout.setSpacing( 2 )
            self.date_editor = DateEditor(parent=self, nullable=True)
            self.date_editor.set_value(default)
            self.query_decorator = query_decorator
            layout.addWidget( self.date_editor )
            self.setLayout( layout )
            self.date_editor.editingFinished.connect(self.emit_filter_changed)

        @QtCore.pyqtSlot()
        def emit_filter_changed(self):
            self.filter_changed_signal.emit()

        def decorate_query(self, query):
            return self.query_decorator(query, self.date_editor.get_value())

    class ValidDateFilter(Filter):
        """Filters entities that are valid a certain date.  This filter will present
        a date to the user and filter the entities that have their from date before this
        date and their end date after this date.  If no date is given, all entities will
        be shown"""

        def __init__(self,
                     from_attribute='from_date',
                     thru_attribute='thru_date',
                     verbose_name=_('Valid at'),
                     default = datetime.date.today ):
            """
            :param from_attribute: the name of the attribute representing the from date
            :param thru_attribute: the name of the attribute representing the thru date
            :param verbose_name: the displayed name of the filter
            :param default: a function returning a default date for the filter
            """
            super(ValidDateFilter, self).__init__(None, default=default)
            self._from_attribute = from_attribute
            self._thru_attribute = thru_attribute
            self._verbose_name = verbose_name

        def render(self, parent, name, options):
            query_decorator, default = options
            return DateFilterWidget(name, query_decorator, default, parent)

        def get_name_and_options(self, admin):
            from sqlalchemy.sql import and_

            def query_decorator(query, date):
                e = admin.entity
                if date:
                    return query.filter(and_(getattr(e, self._from_attribute)<=date,
                                             getattr(e, self._thru_attribute)>=date))
                return query

            return (self._verbose_name, (query_decorator, self._default()))

### Navigation

- index
- modules |
- Camelot documentation »
- Module code »

Comments
blog comments powered by Disqus