Package Camelot :: Package camelot :: Package view :: Module filters |
[frames] | no frames]

# Source Code for Module Camelot.camelot.view.filters

      1  #  ============================================================================

      2  #

      3  #  Copyright (C) 2007-2008 Conceptive Engineering bvba. All rights reserved.

      4  #  www.conceptive.be / project-camelot@conceptive.be

      5  #

      6  #  This file is part of the Camelot Library.

      7  #

      8  #  This file may be used under the terms of the GNU General Public

      9  #  License version 2.0 as published by the Free Software Foundation

     10  #  and appearing in the file LICENSE.GPL included in the packaging of

     11  #  this file.  Please review the following information to ensure GNU

     12  #  General Public Licensing requirements will be met:

     13  #  http://www.trolltech.com/products/qt/opensource.html

     14  #

     15  #  If you are unsure which license is appropriate for your use, please

     16  #  review the following information:

     17  #  http://www.trolltech.com/products/qt/licensing.html or contact

     18  #  project-camelot@conceptive.be.

     19  #

     20  #  This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE

     21  #  WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

     22  #

     23  #  For use of this library in commercial applications, please contact

     24  #  project-camelot@conceptive.be

     25  #

     26  #  ============================================================================

     27

     28  """

     29  Python structures to represent filters.

     30  These structures can be transformed to QT forms.

     31  """
     32

     33  from camelot.view.model_thread import gui_function
     34  from camelot.core.utils import ugettext_lazy as _
     35

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

     38

     39      if structure is an instance of Filter, return structure

     40      else create a GroupBoxFilter from the structure

     41      """
     42      if isinstance(structure, Filter):
     43          return structure
     44      return GroupBoxFilter(structure)
     45

     46 -class Filter(object):
     47      """Base class for filters"""
     48

     49 -    def __init__(self, attribute, value_to_string=lambda x:unicode(x)):
     50          """

     51          @param attribute: the attribute on which to filter, this attribute

     52          may contain dots to indicate relationships that need to be followed,

     53          eg.  'person.groups.name'

     54          @param value_to_string: function that converts a value of the attribute to

     55          a string that will be displayed in the filter

     56          """
     57          self.attribute = attribute
     58          self._value_to_string = value_to_string
     59

     60      @gui_function
     61 -    def render(self, parent, name, options):
     62          """Render this filter as a qt object

     63          @param parent: its parent widget

     64          @param name: the name of the filter

     65          @param options: the options that can be selected, where each option is a list

     66          of tuples containting (option_name, query_decorator)

     67

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

     71 -    def get_name_and_options(self, admin):
     72          """return a tuple of the name of the filter and a list of options that can be selected.

     73          Each option is a tuple of the name of the option, and a filter function to

     74          decorate a query

     75          @return:  (filter_name, [(option_name, query_decorator), ...)

     76          """
     77          from sqlalchemy.sql import select
     78          from sqlalchemy import orm
     79          from elixir import session
     80          filter_names = []
     81          joins = []
     82          table = admin.entity.table
     83          path = self.attribute.split('.')
     84          for field_name in path:
     85              attributes = admin.get_field_attributes(field_name)
     86              filter_names.append(attributes['name'])
     87              # @todo: if the filter is not on an attribute of the relation, but on the relation itselves

     88              if 'target' in attributes:
     89                  admin = attributes['admin']
     90                  joins.append(field_name)
     91                  if attributes['direction'] == orm.interfaces.MANYTOONE:
     92                      table = admin.entity.table.join(table)
     93                  else:
     94                      table = admin.entity.table
     95

     96

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

    100          def create_decorator(col, value, joins):
    101

    102              def decorator(q):
    103                  if joins:
    104                      q = q.join(joins, aliased=True)
    105                  return q.filter(col==value)
    106

    107              return decorator
    108

    109          options = [(self._value_to_string(value[0]), create_decorator(col, value[0], joins))

    110                     for value in session.execute(query)]
    111

    112          return (filter_names[0],[(_('all'), lambda q: q)] + options)
    113

    114 -class GroupBoxFilter(Filter):
    115      """Filter where the items are displayed in a QGroupBox"""
    116

    117      @gui_function
    118 -    def render(self, parent, name, options):
    119

    120          from PyQt4 import QtCore, QtGui
    121          from camelot.view.controls.filterlist import filter_changed_signal
    122

    123          class FilterWidget(QtGui.QGroupBox):
    124              """A box containing a filter that can be applied on a table view, this filter is

    125              based on the distinct values in a certain column"""
    126

    127              def __init__(self, name, choices, parent):
    128                  QtGui.QGroupBox.__init__(self, unicode(name), parent)
    129                  self.group = QtGui.QButtonGroup(self)
    130                  self.item = name
    131                  self.unique_values = []
    132                  self.choices = None
    133                  self.setChoices(choices)
    134

    135              def emit_filter_changed(self, state):
    136                  self.emit(filter_changed_signal)
    137

    138              def setChoices(self, choices):
    139                  self.choices = choices
    140                  layout = QtGui.QVBoxLayout()
    141                  for i,name in enumerate([unicode(c[0]) for c in choices]):
    142                      button = QtGui.QRadioButton(name, self)
    143                      layout.addWidget(button)
    144                      self.group.addButton(button, i)
    145                      if i==0:
    146                          button.setChecked(True)
    147                      self.connect(button, QtCore.SIGNAL('toggled(bool)'), self.emit_filter_changed)
    148                  layout.addStretch()
    149                  self.setLayout(layout)
    150

    151              def decorate_query(self, query):
    152                  checked = self.group.checkedId()
    153                  if checked>=0:
    154                      return self.choices[checked][1](query)
    155                  return query
    156

    157          return FilterWidget(name, options, parent)
    158

    159 -class ComboBoxFilter(Filter):
    160      """Filter where the items are displayed in a QComboBox"""
    161

    162      @gui_function
    163 -    def render(self, parent, name, options):
    164

    165          from PyQt4 import QtCore, QtGui
    166          from camelot.view.controls.filterlist import filter_changed_signal
    167

    168          class FilterWidget(QtGui.QGroupBox):
    169

    170              def __init__(self, name, choices, parent):
    171                  QtGui.QGroupBox.__init__(self, unicode(name), parent)
    172                  layout = QtGui.QVBoxLayout()
    173                  self.choices = choices
    174                  combobox = QtGui.QComboBox(self)
    175                  for i,(name,decorator) in enumerate(choices):
    176                      combobox.insertItem(i, unicode(name), QtCore.QVariant(decorator))
    177                  layout.addWidget(combobox)
    178                  self.setLayout(layout)
    179                  self.current_index = 0
    180                  self.connect(combobox, QtCore.SIGNAL('currentIndexChanged(int)'), self.emit_filter_changed)
    181

    182              def emit_filter_changed(self, index):
    183                  self.current_index = index
    184                  self.emit(filter_changed_signal)
    185

    186              def decorate_query(self, query):
    187                  if self.current_index>=0:
    188                      return self.choices[self.current_index][1](query)
    189                  return query
    190

    191          return FilterWidget(name, options, parent)
    192

    193 -class EditorFilter(Filter):
    194      """Filter that presents the user with an editor, allowing the user to enter

    195      a value on which to filter, and at the same time to show 'All' or 'None'

    196      """
    197

    198 -    def __init__(self, field_name, verbose_name=None):
    199          """:param field: the name of the field on which to filter"""
    200          super(EditorFilter, self).__init__(field_name)
    201          self._field_name = field_name
    202          self._verbose_name = verbose_name
    203

    204 -    def render(self, parent, name, options):
    205          from camelot.view.controls.filter_operator import FilterOperator
    206          entity, field_name, field_attributes = options
    207          return FilterOperator(entity, field_name, field_attributes, parent)
    208

    209 -    def get_name_and_options(self, admin):
    210          field_attributes = admin.get_field_attributes(self._field_name)
    211          name = self._verbose_name or field_attributes['name']
    212          return name, (admin.entity, self._field_name, field_attributes)
    213

    214 -class ValidDateFilter(Filter):
    215      """Filters entities that are valid a certain date.  This filter will present

    216      a date to the user and filter the entities that have their from date before this

    217      date and their end date after this date.  If no date is given, all entities will

    218      be shown"""
    219
    220 -    def __init__(self, from_attribute='from_date', thru_attribute='thru_date', verbose_name=_('Valid at')):
    221          """

    222          :param from_attribute: the name of the attribute representing the from date

    223          :param thru_attribute: the name of the attribute representing the thru date

    224          :param verbose_name: the displayed name of the filter"""
    225          self._from_attribute = from_attribute
    226          self._thru_attribute = thru_attribute
    227          self._verbose_name = verbose_name
    228

    229 -    def render(self, parent, name, options):
    230

    231          from datetime import date
    232          from PyQt4 import QtGui, QtCore
    233          from camelot.view.controls.filterlist import filter_changed_signal
    234          from camelot.view.controls.editors import DateEditor, editingFinished
    235

    236          class FilterWidget(QtGui.QGroupBox):
    237

    238              def __init__(self, name, query_decorator, parent):
    239                  QtGui.QGroupBox.__init__(self, unicode(name), parent)
    240                  layout = QtGui.QVBoxLayout()
    241                  self.date_editor = DateEditor(parent=self, nullable=True)
    242                  self.date_editor.set_value(date.today())
    243                  self.query_decorator = query_decorator
    244                  layout.addWidget(self.date_editor)
    245                  self.setLayout(layout)
    246                  self.connect(self.date_editor, editingFinished, self.emit_filter_changed)
    247

    248              def emit_filter_changed(self):
    249                  self.emit(filter_changed_signal)
    250

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

    254          return FilterWidget(name, options, parent)
    255

    256 -    def get_name_and_options(self, admin):
    257          from sqlalchemy.sql import and_
    258

    259          def query_decorator(query, date):
    260              e = admin.entity
    261              if date:
    262                  return query.filter(and_(getattr(e, self._from_attribute)<=date,

    263                                           getattr(e, self._thru_attribute)>=date))
    264              return query
    265

    266          return (self._verbose_name, query_decorator)
    267