# ============================================================================
#
# 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 _
[docs]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)
[docs]class Filter(object):
"""Base class for filters"""
[docs] 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
[docs] 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()
[docs] 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)
[docs]class GroupBoxFilter(Filter):
"""Filter where the items are displayed in a QGroupBox"""
@gui_function
[docs] def render(self, parent, name, options):
return FilterWidget(name, options, parent)
[docs]class ComboBoxFilter(Filter):
"""Filter where the items are displayed in a QComboBox"""
@gui_function
[docs] def render(self, parent, name, options):
return GroupBoxFilterWidget(name, options, parent)
[docs]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
[docs] 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)
[docs] 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)
[docs]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
[docs] def render(self, parent, name, options):
query_decorator, default = options
return DateFilterWidget(name, query_decorator, default, parent)
[docs] 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()))