def _create_search(self): self.search = TransactionSearchContainer( self, columns=self._get_columns(self.model.kind)) self.query = StoqlibQueryExecuter(self.app.store) self.search.set_query_executer(self.query) self.search.results.connect('row-activated', self._on_row__activated) self.results = self.search.results tree_view = self.search.results.get_treeview() tree_view.set_rules_hint(True) tree_view.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH) self.search.enable_advanced_search()
def setup_slaves(self): self.search = StoqlibSearchSlaveDelegate(self._get_columns(), restore_name=self.__class__.__name__) self.search.enable_advanced_search() self.attach_slave('place_holder', self.search) self.executer = StoqlibQueryExecuter(self.store) self.search.set_query_executer(self.executer) self.executer.set_table(LoanView) self.executer.add_query_callback(self.get_extra_query) self._create_filters() self.search.results.connect('selection-changed', self._on_results_selection_changed) self.search.focus_search_entry()
def _setup_slaves(self): self.search = StoqlibSearchSlaveDelegate(self._get_columns(), restore_name=self.__class__.__name__) self.attach_slave('search_group_holder', self.search) executer = StoqlibQueryExecuter(self.store) executer.set_table(QuotationView) self.search.set_query_executer(executer) self.search.set_text_field_columns(['supplier_name']) filter = self.search.get_primary_filter() filter.set_label(_(u'Supplier:')) self.search.focus_search_entry() self.search.results.connect('selection-changed', self._on_searchlist__selection_changed) self.search.results.connect('row-activated', self._on_searchlist__row_activated) date_filter = DateSearchFilter(_('Date:')) self.search.add_filter(date_filter, columns=['open_date', 'deadline']) self.edit_button.set_sensitive(False) self.remove_button.set_sensitive(False)
def _date_query(self, search_table, column): sfilter = object() executer = StoqlibQueryExecuter(self.store) executer.set_filter_columns(sfilter, [column]) executer.set_table(search_table) state = DateIntervalQueryState(filter=sfilter, start=self.start, end=self.end) return executer.search([state])
def __init__(self, store, table=None, search_table=None, hide_footer=True, title='', selection_mode=None, double_click_confirm=False): """ A base class for search dialog inheritance :param store: a store :param table: :param search_table: :param hide_footer: :param title: :param selection_mode: :param double_click_confirm: If double click a item in the list should automatically confirm """ self.store = store self.search_table = self._setup_search_table(table, search_table) self.selection_mode = self._setup_selection_mode(selection_mode) self.summary_label = None self.double_click_confirm = double_click_confirm BasicDialog.__init__(self, hide_footer=hide_footer, main_label_text=self.main_label_text, title=title or self.title, size=self.size) self.executer = StoqlibQueryExecuter(store) # FIXME: Remove this limit, but we need to migrate all existing # searches to use lazy lists first. That in turn require # us to rewrite the queries in such a way that count(*) # will work properly. self.executer.set_limit(sysparam(self.store).MAX_SEARCH_RESULTS) self.set_table(self.search_table) self.enable_window_controls() self.disable_ok() self.set_ok_label(_('Se_lect Items')) self._setup_search() self._setup_details_slave() self.create_filters() self.setup_widgets()
class SearchDialog(BasicDialog): """ Base class for *all* the search dialogs, responsible for the list construction and "Filter" and "Clear" buttons management. This class must be subclassed and its subclass *must* implement the methods 'get_columns' and 'get_query_and_args' (if desired, 'get_query_and_args' can be implemented in the user's slave class, so SearchDialog will get its slave instance and call the method directly). Its subclass also must implement a setup_slaves method and call its equivalent base class method as in: >>> def setup_slave(self): ... SearchDialog.setup_slaves(self) or then, call it in its constructor, like: >>> def __init__(self, *args): ... SearchDialog.__init__(self) :cvar table: the table type which we will query on to get the objects. :cvar searchbar_labels: labels for SearchBar entry and date fields :cvar searchbar_result_strings: a tuple where each item has a singular and a plural form for searchbar results label :cvar advanced_search: If the advanced search is enabled or disabled """ main_label_text = '' title = '' table = None search_table = None search_labels = None selection_mode = gtk.SELECTION_BROWSE size = () advanced_search = True tree = False @argcheck(object, object, object, bool, basestring, int, bool) def __init__(self, store, table=None, search_table=None, hide_footer=True, title='', selection_mode=None, double_click_confirm=False): """ A base class for search dialog inheritance :param store: a store :param table: :param search_table: :param hide_footer: :param title: :param selection_mode: :param double_click_confirm: If double click a item in the list should automatically confirm """ self.store = store self.search_table = self._setup_search_table(table, search_table) self.selection_mode = self._setup_selection_mode(selection_mode) self.summary_label = None self.double_click_confirm = double_click_confirm BasicDialog.__init__(self, hide_footer=hide_footer, main_label_text=self.main_label_text, title=title or self.title, size=self.size) self.executer = StoqlibQueryExecuter(store) # FIXME: Remove this limit, but we need to migrate all existing # searches to use lazy lists first. That in turn require # us to rewrite the queries in such a way that count(*) # will work properly. self.executer.set_limit(sysparam(self.store).MAX_SEARCH_RESULTS) self.set_table(self.search_table) self.enable_window_controls() self.disable_ok() self.set_ok_label(_('Se_lect Items')) self._setup_search() self._setup_details_slave() self.create_filters() self.setup_widgets() def _setup_search_table(self, table, search_table): search_table = search_table or self.search_table table = table or self.table if not (table or search_table): raise ValueError( "%r must define a table or search_table attribute" % self) return search_table or table def _setup_selection_mode(self, selection_mode): # For consistency do not allow none or single, in other words, # only allowed values are browse and multiple so we always will # be able to use both the keyboard and the mouse to select items # in the search list. selection_mode = selection_mode or self.selection_mode if (selection_mode != gtk.SELECTION_BROWSE and selection_mode != gtk.SELECTION_MULTIPLE): raise ValueError('Invalid selection mode %r' % selection_mode) return selection_mode def _setup_search(self): self.search = StoqlibSearchSlaveDelegate( self.get_columns(), tree=self.tree, restore_name=self.__class__.__name__, ) self.search.set_query_executer(self.executer) if self.advanced_search: self.search.enable_advanced_search() self.attach_slave('main', self.search) self.header.hide() self.results = self.search.search.results self.results.set_selection_mode(self.selection_mode) self.results.connect('cell-edited', self._on_results__cell_edited) self.results.connect('selection-changed', self._on_results__selection_changed) self.results.connect('row-activated', self._on_results__row_activated) self.search.search.connect("search-completed", self._on_search__search_completed) def _setup_details_slave(self): # FIXME: Gross hack has_details_btn = hasattr(self, 'on_details_button_clicked') has_print_btn = hasattr(self, 'on_print_button_clicked') if not (has_details_btn or has_print_btn): self._details_slave = None return self._details_slave = _SearchDialogDetailsSlave() self.attach_slave('details_holder', self._details_slave) if has_details_btn: self._details_slave.connect("details", self.on_details_button_clicked) else: self._details_slave.details_button.hide() if has_print_btn: self._details_slave.connect("print", self.on_print_button_clicked) self.set_print_button_sensitive(False) self.results.connect('has-rows', self._has_rows) else: self._details_slave.print_button.hide() # # Public API # def add_button(self, label, stock=None, image=None): """Adds a button in the bottom of the dialog. :param label: the text that will be displayed by the button. :param stock: the gtk stock id to be used in the button. :param image: the image filename. """ button = gtk.Button(label=label) if image: image_widget = gtk.Image() image_widget.set_from_file( environ.find_resource('pixmaps', image)) image_widget.show() button.set_image(image_widget) elif stock: button_set_image_with_label(button, stock, label) self.action_area.set_layout(gtk.BUTTONBOX_START) self.action_area.pack_start(button, False, False, 6) return button @argcheck(bool) def set_details_button_sensitive(self, value): self._details_slave.details_button.set_sensitive(value) @argcheck(bool) def set_print_button_sensitive(self, value): self._details_slave.print_button.set_sensitive(value) def get_selection(self): mode = self.results.get_selection_mode() if mode == gtk.SELECTION_BROWSE: return self.results.get_selected() return self.results.get_selected_rows() def confirm(self, retval=None): """Confirms the dialog :param retval: optional parameter which will be selected when the dialog is closed """ if retval is None: retval = self.get_selection() self.retval = retval self.search.save_columns() # FIXME: This should chain up so the "confirm" signal gets emitted self.close() def cancel(self, *args): self.retval = [] self.search.save_columns() # FIXME: This should chain up so the "cancel" signal gets emitted self.close() def set_table(self, table): self.executer.set_table(table) self.search_table = table # FIXME: -> remove/use def set_searchbar_labels(self, *args): search_filter = self.search.get_primary_filter() search_filter.set_label(args[0]) def set_searchbar_search_string(self, string): if string == self.get_searchbar_search_string(): return search_filter = self.search.get_primary_filter() search_filter.entry.set_text(string) def get_searchbar_search_string(self): search_filter = self.search.get_primary_filter() return search_filter.get_state().text def set_result_strings(self, *args): pass def set_text_field_columns(self, columns): """See :class:`SearchSlaveDelegate.set_text_field_columns` """ self.search.set_text_field_columns(columns) def disable_search_entry(self): """See :class:`SearchSlaveDelegate.disable_search_entry` """ self.search.disable_search_entry() def add_filter(self, search_filter, position=SearchFilterPosition.BOTTOM, columns=None, callback=None): """See :class:`SearchSlaveDelegate.add_filter` """ self.search.add_filter(search_filter, position, columns, callback) def row_activate(self, obj): """This is called when an item in the results list is double clicked. :param obj: the item that was double clicked. """ if self.double_click_confirm: # But only if its also confirmable with ok_button if self.ok_button.props.sensitive: self.confirm() # # Filters # def create_branch_filter(self, label=None): from stoqlib.domain.person import Branch branches = Branch.get_active_branches(self.store) items = [(b.person.name, b.id) for b in branches] #if not items: # raise ValueError('You should have at least one branch at ' # 'this point') items.insert(0, (_("Any"), None)) if not label: label = _('Branch:') branch_filter = ComboSearchFilter(label, items) current = api.get_current_branch(self.store) if current: branch_filter.select(current.id) return branch_filter def create_payment_filter(self, label=None): from stoqlib.domain.payment.method import PaymentMethod methods = PaymentMethod.get_active_methods(self.store) items = [(_('Any'), None)] for method in methods: if method.method_name == 'multiple': continue items.append((method.description, method)) if not label: label = _('Method:') payment_filter = ComboSearchFilter(label, items) payment_filter.select(None) return payment_filter def create_provider_filter(self, label=None): from stoqlib.domain.payment.card import CreditProvider providers = CreditProvider.get_card_providers(self.store) items = [(p.short_name, p) for p in providers] items.insert(0, (_("Any"), None)) if not label: label = _('Provider:') provider_filter = ComboSearchFilter(label, items) return provider_filter # # Callbacks # def _on_search__search_completed(self, search, results, states): self.search_completed(results, states) def _on_results__cell_edited(self, results, obj, attr): """Override this method on child when it's needed to perform some tasks when editing a row. """ def _on_results__selection_changed(self, results, selected): self.update_widgets() if selected: self.enable_ok() else: self.disable_ok() def _on_results__row_activated(self, results, obj): self.row_activate(obj) def _has_rows(self, results, obj): self.set_print_button_sensitive(obj) # # Hooks # def create_filters(self): raise NotImplementedError( "create_filters() must be implemented in %r" % self) def setup_widgets(self): pass def get_columns(self): raise NotImplementedError( "get_columns() must be implemented in %r" % self) def update_widgets(self): """Subclass can have an 'update_widgets', and this method will be called when a signal is emitted by 'Filter' or 'Clear' buttons and also when a list item is selected. """ def search_completed(self, results, states): pass
class LoanSelectionStep(BaseWizardStep): gladefile = 'HolderTemplate' def __init__(self, wizard, store): BaseWizardStep.__init__(self, store, wizard) self.setup_slaves() def _create_filters(self): self.search.set_text_field_columns(['client_name']) def _get_columns(self): return [SearchColumn('identifier', title=_('#'), sorted=True, data_type=int), SearchColumn('responsible_name', title=_(u'Responsible'), data_type=str, expand=True), SearchColumn('client_name', title=_(u'Client'), data_type=str, expand=True), SearchColumn('open_date', title=_(u'Opened'), data_type=datetime.date), SearchColumn('expire_date', title=_(u'Expire'), data_type=datetime.date), Column('loaned', title=_(u'Loaned'), data_type=Decimal), ] def _refresh_next(self, value=None): has_selected = self.search.results.get_selected() is not None self.wizard.refresh_next(has_selected) def get_extra_query(self, states): return LoanView.status == Loan.STATUS_OPEN def setup_slaves(self): self.search = StoqlibSearchSlaveDelegate(self._get_columns(), restore_name=self.__class__.__name__) self.search.enable_advanced_search() self.attach_slave('place_holder', self.search) self.executer = StoqlibQueryExecuter(self.store) self.search.set_query_executer(self.executer) self.executer.set_table(LoanView) self.executer.add_query_callback(self.get_extra_query) self._create_filters() self.search.results.connect('selection-changed', self._on_results_selection_changed) self.search.focus_search_entry() # # WizardStep # def has_previous_step(self): return False def post_init(self): self.register_validate_function(self._refresh_next) self.force_validation() def next_step(self): loan = self.search.results.get_selected().loan # FIXME: For some reason, the loan isn't in self.store self.wizard.model = self.store.fetch(loan) return LoanItemSelectionStep(self.wizard, self, self.store, self.wizard.model) # # Callbacks # def _on_results_selection_changed(self, widget, selection): self._refresh_next()
def _date_filter_query(self, search_table, column): executer = StoqlibQueryExecuter(self.store) executer.set_filter_columns(self.date_filter, [column]) executer.set_table(search_table) return executer.search([self.date_filter.get_state()])
class TransactionPage(object): # shows either a list of: # - transactions # - payments def __init__(self, model, app, parent): self.model = model self.app = app self.parent_window = parent self._block = False self._create_search() self._add_date_filter() self._setup_search() self.refresh() def get_toplevel(self): return self.parent_window def _create_search(self): self.search = TransactionSearchContainer( self, columns=self._get_columns(self.model.kind)) self.query = StoqlibQueryExecuter(self.app.store) self.search.set_query_executer(self.query) self.search.results.connect('row-activated', self._on_row__activated) self.results = self.search.results tree_view = self.search.results.get_treeview() tree_view.set_rules_hint(True) tree_view.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH) self.search.enable_advanced_search() def _add_date_filter(self): self.date_filter = DateSearchFilter(_('Date:')) self.date_filter.clear_options() self.date_filter.add_option(Any, 0) year = datetime.datetime.today().year month_names = get_month_names() for i, month in enumerate(month_names): name = month_names[i] option = type(name + 'Option', (MonthOption, ), {'name': _(name), 'month': i + 1, 'year': year}) self.date_filter.add_option(option, i + 1) self.date_filter.add_custom_options() self.date_filter.mode.select_item_by_position(0) self.search.add_filter(self.date_filter) def _append_date_query(self, field): date = self.date_filter.get_state() queries = [] if isinstance(date, DateQueryState) and date.date is not None: queries.append(Date(field) == date.date) elif isinstance(date, DateIntervalQueryState): queries.append(Date(field) >= date.start) queries.append(Date(field) <= date.end) return queries def _payment_query(self, store): queries = self._append_date_query(self.query.table.due_date) if queries: return store.find(self.query.table, And(*queries)) return store.find(self.query.table) def _transaction_query(self, store): queries = [Or(self.model.id == AccountTransaction.account_id, self.model.id == AccountTransaction.source_account_id)] queries.extend(self._append_date_query(AccountTransaction.date)) return store.find(AccountTransactionView, And(*queries)) def show(self): self.search.show() def _setup_search(self): if self.model.kind == 'account': self.query.set_table(AccountTransactionView) self.search.set_text_field_columns(['description']) self.query.set_query(self._transaction_query) elif self.model.kind == 'payable': self.search.set_text_field_columns(['description', 'supplier_name']) self.query.set_table(OutPaymentView) self.query.set_query(self._payment_query) elif self.model.kind == 'receivable': self.search.set_text_field_columns(['description', 'drawee']) self.query.set_table(InPaymentView) self.query.set_query(self._payment_query) else: raise TypeError("unknown model kind: %r" % (self.model.kind, )) def refresh(self): self.search.results.clear() if self.model.kind == 'account': transactions = AccountTransactionView.get_for_account(self.model, self.app.store) self.append_transactions(transactions) elif self.model.kind == 'payable': self._populate_payable_payments(OutPaymentView) elif self.model.kind == 'receivable': self._populate_payable_payments(InPaymentView) else: raise TypeError("unknown model kind: %r" % (self.model.kind, )) def _get_columns(self, kind): if kind in ['payable', 'receivable']: return self._get_payment_columns() else: return self._get_account_columns() def _get_account_columns(self): def format_withdrawal(value): if value < 0: return currency(abs(value)).format(symbol=True, precision=2) def format_deposit(value): if value > 0: return currency(value).format(symbol=True, precision=2) if self.model.account_type == Account.TYPE_INCOME: color_func = lambda x: False else: color_func = lambda x: x < 0 return [Column('date', title=_("Date"), data_type=datetime.date, sorted=True), Column('code', title=_("Code"), data_type=unicode), Column('description', title=_("Description"), data_type=unicode, expand=True), Column('account', title=_("Account"), data_type=unicode), Column('value', title=self.model.account.get_type_label(out=False), data_type=currency, format_func=format_deposit), Column('value', title=self.model.account.get_type_label(out=True), data_type=currency, format_func=format_withdrawal), ColoredColumn('total', title=_("Total"), data_type=currency, color='red', data_func=color_func)] def _get_payment_columns(self): return [SearchColumn('due_date', title=_("Due date"), data_type=datetime.date, sorted=True), SearchColumn('identifier', title=_("Code"), data_type=int), SearchColumn('description', title=_("Description"), data_type=unicode, expand=True), SearchColumn('value', title=_("Value"), data_type=currency)] def append_transactions(self, transactions): for transaction in transactions: description = transaction.get_account_description(self.model) value = transaction.get_value(self.model) self._add_transaction(transaction, description, value) self.update_totals() def _populate_payable_payments(self, view_class): for view in self.app.store.find(view_class): self.search.results.append(view) def _add_transaction(self, transaction, description, value): item = Settable(transaction=transaction) self._update_transaction(item, transaction, description, value) self.search.results.append(item) return item def _update_transaction(self, item, transaction, description, value): item.account = description item.date = transaction.date item.description = transaction.description item.value = value item.code = transaction.code def update_totals(self): total = decimal.Decimal('0') for item in self.search.results: total += item.value item.total = total def _edit_transaction_dialog(self, item): store = api.new_store() if isinstance(item.transaction, AccountTransactionView): account_transaction = store.fetch(item.transaction.transaction) else: account_transaction = store.fetch(item.transaction) model = getattr(self.model, 'account', self.model) transaction = run_dialog(AccountTransactionEditor, self.app, store, account_transaction, model) if transaction: store.flush() self._update_transaction(item, transaction, transaction.edited_account.description, transaction.value) self.update_totals() self.search.results.update(item) self.app.accounts.refresh_accounts(self.app.store) store.confirm(transaction) store.close() def on_dialog__opened(self, dialog): dialog.connect('account-added', self.on_dialog__account_added) def on_dialog__account_added(self, dialog): self.app.accounts.refresh_accounts(self.app.store) def add_transaction_dialog(self): store = api.new_store() model = getattr(self.model, 'account', self.model) model = store.fetch(model) transaction = run_dialog(AccountTransactionEditor, self.app, store, None, model) if transaction: transaction.sync() value = transaction.value other = transaction.get_other_account(model) if other == model: value = -value item = self._add_transaction(transaction, other.description, value) self.update_totals() self.search.results.update(item) self.app.accounts.refresh_accounts(self.app.store) store.confirm(transaction) store.close() def _on_row__activated(self, objectlist, item): if self.model.kind == 'account': self._edit_transaction_dialog(item)