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