Ejemplo n.º 1
0
    def _setup_search(self):
        self.columns = self.get_columns()
        SearchDialogSetupSearchEvent.emit(self)
        if self.save_columns:
            restore_name = self.__class__.__name__
        else:
            restore_name = None
        self.search = SearchSlave(
            self.columns,
            tree=self.tree,
            restore_name=restore_name,
            store=self.store,
            search_spec=self.search_spec,
            fast_iter=self.fast_iter,
            result_view_class=self.result_view_class
        )
        if self.advanced_search:
            self.search.enable_advanced_search()
        self.attach_slave('main', self.search)
        self.header.hide()

        self.results = self.search.result_view
        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)
Ejemplo n.º 2
0
    def __init__(
        self, store, columns=None, editor_class=None, klist_objects=None, visual_mode=False, restore_name=None
    ):
        """ Creates a new AdditionListSlave object

        :param store:         a store
        :param columns:       column definitions
        :type columns:        sequence of :class:`kiwi.ui.widgets.list.Columns`
        :param editor_class:  the window that is going to be open when user
                              clicks on add_button or edit_button.
        :type: editor_class:  a :class:`stoqlib.gui.editors.BaseEditor` subclass
        :param klist_objects: initial objects to insert into the list
        :param visual_mode:   if we are working on visual mode, that means,
                              not possible to edit the model on this object
        type visual_mode:     bool
        :param restore_name:  the name used to save and restore the columns
                              on a cache system (e.g. pickle)
        :type restore_name:   basestring
        """
        columns = columns or self.get_columns()
        SearchSlave.__init__(self, columns=columns, restore_name=restore_name, store=store)
        if not self.columns:
            raise StoqlibError("columns must be specified")
        self.visual_mode = visual_mode
        self.store = store
        self.set_editor(editor_class)
        self._can_edit = True
        self._callback_id = None
        if self.visual_mode:
            self.hide_add_button()
            self.hide_edit_button()
            self.hide_del_button()
        items = klist_objects or self.get_items()
        self._setup_klist(items)
        self._update_sensitivity()
Ejemplo n.º 3
0
 def _create_search(self):
     if self.search_spec is None:
         return
     self.search = SearchSlave(self.get_columns(),
                               store=self.store,
                               restore_name=self.__class__.__name__,
                               search_spec=self.search_spec)
Ejemplo n.º 4
0
 def _create_search(self):
     if self.search_spec is None:
         return
     self.columns = self.get_columns()
     ApplicationSetupSearchEvent.emit(self)
     self.search = SearchSlave(self.columns,
                               store=self.store,
                               restore_name=self.__class__.__name__,
                               search_spec=self.search_spec)
Ejemplo n.º 5
0
 def _create_search(self):
     self.search = SearchSlave(self._get_columns(self.model.kind),
                               store=self.app.store)
     self.search.connect('result-item-activated',
                         self._on_search__item_activated)
     self.search.enable_advanced_search()
     self.search.set_result_view(FinancialSearchResults)
     self.result_view = self.search.result_view
     self.result_view.page = self
     tree_view = self.search.result_view.get_treeview()
     tree_view.set_rules_hint(True)
     tree_view.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH)
Ejemplo n.º 6
0
 def _create_search(self):
     self.search = SearchSlave(self._get_columns(self.model.kind),
                               store=self.app.store)
     self.search.connect('result-item-activated',
                         self._on_search__item_activated)
     self.search.enable_advanced_search()
     self.search.set_result_view(FinancialSearchResults)
     self.result_view = self.search.result_view
     self.result_view.page = self
     self.result_view.set_cell_data_func(self._on_result_view__cell_data_func)
     tree_view = self.search.result_view.get_treeview()
     tree_view.set_rules_hint(True)
     tree_view.set_grid_lines(Gtk.TreeViewGridLines.BOTH)
Ejemplo n.º 7
0
    def __init__(self,
                 store,
                 columns=None,
                 editor_class=None,
                 klist_objects=None,
                 visual_mode=False,
                 restore_name=None,
                 tree=False):
        """ Creates a new AdditionListSlave object

        :param store:         a store
        :param columns:       column definitions
        :type columns:        sequence of :class:`kiwi.ui.objectlist.Columns`
        :param editor_class:  the window that is going to be open when user
                              clicks on add_button or edit_button.
        :type: editor_class:  a :class:`stoqlib.gui.editors.BaseEditor` subclass
        :param klist_objects: initial objects to insert into the list
        :param visual_mode:   if we are working on visual mode, that means,
                              not possible to edit the model on this object
        type visual_mode:     bool
        :param restore_name:  the name used to save and restore the columns
                              on a cache system (e.g. pickle)
        :type restore_name:   basestring
        :param tree:          Indication of which kind of list we are adding.
                              If `True` ObjectTree otherwise ObjectList will be
                              added
        """
        columns = columns or self.get_columns()
        SearchSlave.__init__(self,
                             columns=columns,
                             restore_name=restore_name,
                             store=store)
        self.tree = tree
        self.klist = ObjectTree() if tree else ObjectList()
        self.list_vbox.add(self.klist)
        self.list_vbox.show_all()

        if not self.columns:
            raise StoqlibError("columns must be specified")
        self.visual_mode = visual_mode
        self.store = store
        self.set_editor(editor_class)
        self._can_edit = True
        self._callback_id = None
        if self.visual_mode:
            self.hide_add_button()
            self.hide_edit_button()
            self.hide_del_button()
        items = klist_objects or self.get_items()
        self._setup_klist(items)
        self._update_sensitivity()
Ejemplo n.º 8
0
 def setup_slaves(self):
     self.search = SearchSlave(self._get_columns(),
                               restore_name=self.__class__.__name__,
                               store=self.store,
                               search_spec=LoanView)
     self.search.enable_advanced_search()
     self.attach_slave('place_holder', self.search)
     executer = self.search.get_query_executer()
     executer.add_query_callback(self.get_extra_query)
     self._create_filters()
     self.search.results.connect('selection-changed',
                                 self._on_results_selection_changed)
     self.search.results.set_selection_mode(gtk.SELECTION_MULTIPLE)
     self.search.focus_search_entry()
Ejemplo n.º 9
0
 def _create_search(self):
     self.search = SearchSlave(self._get_columns(),
                               restore_name=self.__class__.__name__)
     self.search.enable_advanced_search()
     self.attach_slave('searchbar_holder', self.search)
     self.executer = QueryExecuter(self.store)
     self.search.set_query_executer(self.executer)
     self.executer.set_table(PurchaseOrderView)
     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.results.connect('row-activated',
                                 self._on_results__row_activated)
     self.search.focus_search_entry()
Ejemplo n.º 10
0
    def _setup_search(self):
        self.columns = self.get_columns()
        SearchDialogSetupSearchEvent.emit(self)
        if self.save_columns:
            restore_name = self.__class__.__name__
        else:
            restore_name = None
        self.search = SearchSlave(
            self.columns,
            tree=self.tree,
            restore_name=restore_name,
            store=self.store,
            search_spec=self.search_spec,
            fast_iter=self.fast_iter,
            result_view_class=self.result_view_class
        )
        if self.advanced_search:
            self.search.enable_advanced_search()
        self.attach_slave('main', self.search)
        self.header.hide()

        self.results = self.search.result_view
        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)
Ejemplo n.º 11
0
 def _create_search(self):
     self.search = SearchSlave(self._get_columns(),
                               restore_name=self.__class__.__name__,
                               store=self.store,
                               search_spec=PurchaseOrderView)
     self.search.enable_advanced_search()
     self.attach_slave('searchbar_holder', self.search)
     executer = self.search.get_query_executer()
     executer.add_query_callback(self.get_extra_query)
     self._create_filters()
     self.search.result_view.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
     self.search.result_view.connect('selection-changed',
                                     self._on_results__selection_changed)
     self.search.result_view.connect('row-activated',
                                     self._on_results__row_activated)
     self.search.focus_search_entry()
Ejemplo n.º 12
0
 def _create_search(self):
     if self.search_spec is None:
         return
     self.columns = self.get_columns()
     ApplicationSetupSearchEvent.emit(self)
     self.search = SearchSlave(
         self.columns, store=self.store, restore_name=self.__class__.__name__, search_spec=self.search_spec
     )
Ejemplo n.º 13
0
    def _setup_search(self):
        self.search = SearchSlave(
            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.result_view
        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)
Ejemplo n.º 14
0
    def _setup_slaves(self):
        self.search = SearchSlave(self._get_columns(),
                                  restore_name=self.__class__.__name__,
                                  search_spec=QuotationView,
                                  store=self.store)
        self.attach_slave('search_group_holder', self.search)

        self.search.set_text_field_columns(['supplier_name', 'identifier_str'])
        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)
Ejemplo n.º 15
0
 def setup_slaves(self):
     self.search = SearchSlave(
         self._get_columns(), restore_name=self.__class__.__name__, store=self.store, search_spec=LoanView
     )
     self.search.enable_advanced_search()
     self.attach_slave("place_holder", self.search)
     executer = self.search.get_query_executer()
     executer.add_query_callback(self.get_extra_query)
     self._create_filters()
     self.search.results.connect("selection-changed", self._on_results_selection_changed)
     self.search.results.set_selection_mode(gtk.SELECTION_MULTIPLE)
     self.search.focus_search_entry()
Ejemplo n.º 16
0
 def _create_search(self):
     self.search = SearchSlave(self._get_columns(self.model.kind),
                               store=self.app.store)
     self.search.connect('result-item-activated',
                         self._on_search__item_activated)
     self.search.enable_advanced_search()
     self.search.set_result_view(FinancialSearchResults)
     self.result_view = self.search.result_view
     self.result_view.page = self
     self.result_view.set_cell_data_func(self._on_result_view__cell_data_func)
     tree_view = self.search.result_view.get_treeview()
     tree_view.set_rules_hint(True)
     tree_view.set_grid_lines(Gtk.TreeViewGridLines.BOTH)
Ejemplo n.º 17
0
    def _create_search(self):
        # This does the first part of the search creation,
        # this need to be done here so that self.results is set when we
        # call GladeDelegate.__init__()

        self.executer = QueryExecuter(self.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.executer.set_table(self.search_table)

        self.search = SearchSlave(self.get_columns(),
                                  restore_name=self.__class__.__name__)
        self.search.enable_advanced_search()
        self.search.set_query_executer(self.executer)
        self.search.connect("search-completed",
                            self._on_search__search_completed)
        self.results = self.search.result_view
        search_filter = self.search.get_primary_filter()
        search_filter.set_label(self.search_label)
Ejemplo n.º 18
0
 def _create_search(self):
     self.search = SearchSlave(self._get_columns(),
                               restore_name=self.__class__.__name__,
                               store=self.store,
                               search_spec=PurchaseOrderView)
     self.search.enable_advanced_search()
     self.attach_slave('searchbar_holder', self.search)
     executer = self.search.get_query_executer()
     executer.add_query_callback(self.get_extra_query)
     self._create_filters()
     self.search.results.connect('selection-changed',
                                 self._on_results__selection_changed)
     self.search.results.connect('row-activated',
                                 self._on_results__row_activated)
     self.search.focus_search_entry()
Ejemplo n.º 19
0
    def _setup_search(self):
        self.search = SearchSlave(
            self.get_columns(),
            tree=self.tree,
            restore_name=self.__class__.__name__,
            store=self.store,
            search_spec=self.search_spec,
        )
        if self.advanced_search:
            self.search.enable_advanced_search()
        self.attach_slave("main", self.search)
        self.header.hide()

        self.results = self.search.result_view
        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)
Ejemplo n.º 20
0
    def _setup_slaves(self):
        self.search = SearchSlave(self._get_columns(),
                                  restore_name=self.__class__.__name__,
                                  search_spec=QuotationView,
                                  store=self.store)
        self.attach_slave('search_group_holder', self.search)

        self.search.set_text_field_columns(['supplier_name', 'identifier_str'])
        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)
Ejemplo n.º 21
0
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)
    """
    main_label_text = ''

    #: Title that will appear in the window, for instance 'Product Search'
    title = ''

    # The table type which we will query on to get the objects.
    search_spec = None

    #: The label that will be used for the main filter in this dialog
    search_label = None

    #: Selection mode to use (if its possible to select more than one row)
    selection_mode = gtk.SELECTION_BROWSE

    #: Default size for this dialog
    size = ()

    #: If the advanced search is enabled or disabled. When ``True`` we will
    #: instrospect the columns returned by :meth:`get_columns`,and use those
    #: that are subclasses of :class:`stoqlib.gui.columns.SearchColumn` to add
    #: as options for the user to filter the results.
    advanced_search = True

    tree = False

    def __init__(self, store, search_spec=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_spec:
        :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_spec = search_spec or self.search_spec
        if not self.search_spec:
            raise ValueError("%r needs a search table" % self)
        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.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()
        if self.search_label:
            self.set_searchbar_label(self.search_label)

    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 = SearchSlave(
            self.get_columns(),
            tree=self.tree,
            restore_name=self.__class__.__name__,
            store=self.store,
            search_spec=self.search_spec,
        )
        if self.advanced_search:
            self.search.enable_advanced_search()
        self.attach_slave('main', self.search)
        self.header.hide()

        self.results = self.search.result_view
        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)

    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

    def set_details_button_sensitive(self, value):
        self._details_slave.details_button.set_sensitive(value)

    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()

    # FIXME: -> remove/use

    # TODO: Check if we can remove
    def set_searchbar_label(self, label):
        search_filter = self.search.get_primary_filter()
        search_filter.set_label(label)

    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_text_field_columns(self, columns):
        """See :class:`SearchSlave.set_text_field_columns`
        """
        self.search.set_text_field_columns(columns)

    def disable_search_entry(self):
        """See :class:`SearchSlave.disable_search_entry`
        """
        self.search.disable_search_entry()

    def add_filter(self, search_filter, position=SearchFilterPosition.BOTTOM,
                   columns=None, callback=None):
        """See :class:`SearchSlave.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
Ejemplo n.º 22
0
class ShellApp(GladeDelegate):
    """Base class for shell applications.

    The main use is to interact with a shell window and reduce
    duplication between other applications.
    """

    #: This attribute is used when generating titles for applications.
    #: It's also useful if we get a list of available applications with
    #: the application names translated. This list is going to be used when
    #: creating new user profiles.
    app_title = None

    #: name of the application, 'pos', 'payable', etc
    app_name = None

    #: If this application has a search like interface
    search = None

    #: This dictionary holds information about required permissions to access
    #: certain actions. Keys should be the name of the action (for instance
    #: SearchEmployess), and the value should be a tuple with the permission key
    #: (domain object or action identifier) and the required permission. In this
    #: case: ('Employee', perm.PERM_SEARCH). See <stoqlib.lib.permissions>
    action_permissions = {}

    #: The spec for store.find() to perform the search on
    search_spec = None

    #: Label left of the search entry
    search_label = _('Search:')

    #: the report class for printing the object list embedded on app.
    report_table = None

    def __init__(self, window, store=None):
        if store is None:
            store = api.get_default_store()
        self.store = store
        self.window = window

        self._sensitive_group = dict()
        self.help_ui = None
        self.uimanager = self.window.uimanager

        # FIXME: These two should probably post-__init__, but
        #        that breaks date_label in the calender app
        self._create_search()
        self.create_actions()
        GladeDelegate.__init__(self,
                               gladefile=self.gladefile,
                               toplevel_name=self.toplevel_name)
        self._attach_search()
        self.create_ui()

    def _create_search(self):
        if self.search_spec is None:
            return
        self.columns = self.get_columns()
        ApplicationSetupSearchEvent.emit(self)
        self.search = SearchSlave(self.columns,
                                  store=self.store,
                                  restore_name=self.__class__.__name__,
                                  search_spec=self.search_spec)

    def _attach_search(self):
        if self.search_spec is None:
            return
        self.search.enable_advanced_search()
        self.attach_slave('search_holder', self.search)
        search_filter = self.search.get_primary_filter()
        search_filter.set_label(self.search_label)
        self.create_filters()
        self.search.restore_filter_settings('app-ui', self.app_name)
        self.search.focus_search_entry()

        # FIXME: Remove and use search directly instead of the result view
        self.results = self.search.result_view

    def _display_open_inventory_message(self):
        msg = _(u'There is an inventory process open at the moment.\n'
                'While that inventory is open, you will be unable to do '
                'operations that modify your stock.')
        self.inventory_bar = self.window.add_info_bar(gtk.MESSAGE_WARNING, msg)

    #
    # Overridables
    #

    def create_actions(self):
        """This is called before the BaseWindow constructor, so we
        can create actions that can be autoconnected.
        The widgets and actions loaded from builder files are not set
        yet"""

    def create_ui(self):
        """This is called when the UI such as GtkWidgets should be
        created. Glade widgets are now created and can be accessed
        in the instance.
        """

    def activate(self, refresh=True):
        """This is when you switch to an application.

        You should setup widget sensitivity here and refresh lists etc

        :param refresh: if we should refresh the search
        """

    def setup_focus(self):
        """Define this method on child when it's needed.
        This is for calling grab_focus(), it's called after the window
        is shown. focus chains should be created in create_ui()"""

    def get_title(self):
        # This method must be redefined in child when it's needed
        branch = api.get_current_branch(self.store)
        return _('[%s] - %s') % (branch.get_description(), self.app_title)

    def can_change_application(self):
        """Define if we can change the current application or not.

        :returns: True if we can change the application, False otherwise.
        """
        return True

    def can_close_application(self):
        """Define if we can close the current application or not.

        :returns: True if we can close the application, False otherwise.
        """
        return True

    def set_open_inventory(self):
        """ Subclasses should overide this if they call
        :obj:`.check_open_inventory`.

        This method will be called it there is an open inventory, so the
        application can disable some funcionalities
        """
        raise NotImplementedError

    def new_activate(self):
        """Called when the New toolbar item is activated"""
        raise NotImplementedError

    def search_activate(self):
        """Called when the Search toolbar item is activated"""
        raise NotImplementedError

    def print_activate(self):
        """Called when the Print toolbar item is activated"""
        if self.search_spec is None:
            raise NotImplementedError

        if self.results.get_selection_mode() == gtk.SELECTION_MULTIPLE:
            results = self.results.get_selected_rows()
        else:
            result = self.results.get_selected()
            results = [result] if result else None

        # There are no itens selected. We should print the entire list
        if not results:
            results = list(self.search.get_last_results())
        self.print_report(self.report_table, self.results, results)

    def export_spreadsheet_activate(self):
        """Called when the Export menu item is activated"""
        if self.search_spec is None:
            raise NotImplementedError

        model = self.results.get_model()
        if isinstance(model, LazyObjectModel):
            model.load_items_from_results(0, model._count)

        sse = SpreadSheetExporter()
        sse.export(object_list=self.results,
                   name=self.app_name,
                   filename_prefix=self.app_name)

    def create_filters(self):
        """Implement this to provide filters for the search container"""

    def search_completed(self, results, states):
        """Implement this if you want to know when a search has
        been completed.

        :param results: the search results
        :param states: search states used to construct the search query search
        """

    #
    # Public API
    #

    def add_ui_actions(self, ui_string, actions, name='Actions',
                       action_type='normal', filename=None):
        return self.window.add_ui_actions(ui_string=ui_string,
                                          actions=actions,
                                          name=name,
                                          action_type=action_type,
                                          filename=filename,
                                          instance=self)

    def add_tool_menu_actions(self, actions):
        return self.window.add_tool_menu_actions(actions=actions)

    def add_columns(self, columns):
        """Add some columns to the default ones.

        Note that this method must be called during the setup of this search,
        which right now is only possible for those who capture the
        `<stoqlib.gui.events.ApplicationSetupSearchEvent>`
        """
        self.columns.extend(columns)

    def set_help_section(self, label, section):
        self.window.set_help_section(label=label,
                                     section=section)

    def get_statusbar_message_area(self):
        return self.window.statusbar.message_area

    def print_report(self, report_class, *args, **kwargs):
        filters = self.search.get_search_filters()
        if filters:
            kwargs['filters'] = filters

        print_report(report_class, *args, **kwargs)

    def set_sensitive(self, widgets, value):
        """Set the *widgets* sensitivity based on *value*

        If a sensitive group was registered for any widget,
        it's validation function will be tested and, if ``False``
        is returned, it will be set insensitive, ignoring *value*

        :param widgets: a list of widgets
        :param value: either `True` or `False`
        """
        # FIXME: Maybe this should ne done on kiwi?
        for widget in widgets:
            sensitive = value

            for validator in self._sensitive_group.get(widget, []):
                if not validator[0](*validator[1]):
                    sensitive = False
                    break

            widget.set_sensitive(sensitive)

    def register_sensitive_group(self, widgets, validation_func, *args):
        """Register widgets on a sensitive group.

        Everytime :obj:`.set_sensitive()` is called, if there is any
        validation function for a given widget on sensitive group,
        then that will be used to decide if it gets sensitive or
        insensitive.

        :param widgets: a list of widgets
        :param validation_func: a function for validation. It should
            return either ``True`` or ``False``.
        :param args: args that will be passed to *validation_func*
        """
        assert callable(validation_func)

        for widget in widgets:
            validators = self._sensitive_group.setdefault(widget, set())
            validators.add((validation_func, args))

    def run_dialog(self, dialog_class, *args, **kwargs):
        """ Encapsuled method for running dialogs. """
        return run_dialog(dialog_class, self, *args, **kwargs)

    @cached_function()
    def has_open_inventory(self):
        return Inventory.has_open(self.store,
                                  api.get_current_branch(self.store))

    def check_open_inventory(self):
        """Checks if there is an open inventory.

        In the case there is one, will call set_open_inventory (subclasses
        should implement it).

        Returns True if there is an open inventory. False otherwise
        """
        inventory_bar = getattr(self, 'inventory_bar', None)

        if self.has_open_inventory():
            if inventory_bar:
                inventory_bar.show()
            else:
                self._display_open_inventory_message()
            self.set_open_inventory()
            return True
        elif inventory_bar:
            inventory_bar.hide()
            return False

    # FIXME: Most of these should be removed and access the search API
    #        directly, eg, self.search.clear() etc

    def add_filter(self, search_filter, position=SearchFilterPosition.BOTTOM,
                   columns=None, callback=None):
        """
        See :class:`SearchSlave.add_filter`
        """
        self.search.add_filter(search_filter, position, columns, callback)

    def set_text_field_columns(self, columns):
        """
        See :class:`SearchSlave.set_text_field_columns`
        """
        self.search.set_text_field_columns(columns)

    def refresh(self, rollback=True):
        """
        See :class:`stoqlib.gui.search.searchslave.SearchSlave.refresh`
        """
        # Since the store here is actually a transaction and the items
        # on it can be changed from another station, do a rollback so
        # the items get reloaded, avoiding cache problems
        # Note that this gets mocked on tests to not do the rollback
        if rollback:
            self.store.rollback(close=False)
        self.search.refresh()

    def clear(self):
        """
        See :class:`stoqlib.gui.search.searchslave.SearchSlave.clear`
        """
        self.search.clear()

    def select_result(self, result):
        """Select the object in the result list

        If the object is not in the list (filtered out, for instance), no error
        is thrown and nothing is selected
        """
        try:
            self.results.select(result)
        except ValueError:
            pass

    #
    # Callbacks
    #

    def on_search__search_completed(self, search, results, states):
        self.search_completed(results, states)

        has_results = len(results)
        for widget in [self.window.Print,
                       self.window.ExportSpreadSheet]:
            widget.set_sensitive(has_results)
        self.search.save_filter_settings('app-ui', self.app_name)
Ejemplo n.º 23
0
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 = SearchSlave(self._get_columns(self.model.kind),
                                  store=self.app.store)
        self.search.connect('result-item-activated',
                            self._on_search__item_activated)
        self.search.enable_advanced_search()
        self.search.set_result_view(FinancialSearchResults)
        self.result_view = self.search.result_view
        self.result_view.page = self
        tree_view = self.search.result_view.get_treeview()
        tree_view.set_rules_hint(True)
        tree_view.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH)

    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):
        executer = self.search.get_query_executer()
        search_spec = executer.search_spec
        queries = self._append_date_query(search_spec.due_date)
        if queries:
            return store.find(search_spec, And(*queries))

        return store.find(search_spec)

    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.search.set_search_spec(AccountTransactionView)
            self.search.set_text_field_columns(['description'])
            self.search.set_query(self._transaction_query)
        elif self.model.kind == 'payable':
            self.search.set_text_field_columns(['description', 'supplier_name'])
            self.search.set_search_spec(OutPaymentView)
            self.search.set_query(self._payment_query)
        elif self.model.kind == 'receivable':
            self.search.set_text_field_columns(['description', 'drawee'])
            self.search.set_search_spec(InPaymentView)
            self.search.set_query(self._payment_query)
        else:
            raise TypeError("unknown model kind: %r" % (self.model.kind, ))

    def refresh(self):
        self.search.result_view.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),
                IdentifierColumn('identifier', title=_("Code")),
                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.result_view.append(view)

    def _add_transaction(self, transaction, description, value):
        item = Settable(transaction=transaction)
        self._update_transaction(item, transaction, description, value)
        self.search.result_view.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.result_view:
            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.result_view.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.result_view.update(item)
            self.app.accounts.refresh_accounts(self.app.store)
        store.confirm(transaction)
        store.close()

    def _on_search__item_activated(self, objectlist, item):
        if self.model.kind == 'account':
            self._edit_transaction_dialog(item)
Ejemplo n.º 24
0
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', 'identifier_str'])

    def _get_columns(self):
        return [IdentifierColumn('identifier', sorted=True),
                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):
        can_continue = False
        selected_rows = self.search.results.get_selected_rows()
        if selected_rows:
            client = selected_rows[0].client_id
            branch = selected_rows[0].branch_id
            # Only loans that belong to the same client and are from the same
            # branch can be closed together
            can_continue = all(v.client_id == client and v.branch_id == branch
                               for v in selected_rows)
        self.wizard.refresh_next(can_continue)

    def get_extra_query(self, states):
        return LoanView.status == Loan.STATUS_OPEN

    def setup_slaves(self):
        self.search = SearchSlave(self._get_columns(),
                                  restore_name=self.__class__.__name__,
                                  store=self.store,
                                  search_spec=LoanView)
        self.search.enable_advanced_search()
        self.attach_slave('place_holder', self.search)
        executer = self.search.get_query_executer()
        executer.add_query_callback(self.get_extra_query)
        self._create_filters()
        self.search.results.connect('selection-changed',
                                    self._on_results_selection_changed)
        self.search.results.set_selection_mode(gtk.SELECTION_MULTIPLE)
        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):
        # FIXME: For some reason, the loan isn't in self.store
        views = self.search.results.get_selected_rows()
        self.wizard.models = [self.store.fetch(v.loan) for v in views]
        return LoanItemSelectionStep(self.wizard, self, self.store,
                                     self.wizard.models)

    #
    # Callbacks
    #

    def _on_results_selection_changed(self, widget, selection):
        self._refresh_next()
Ejemplo n.º 25
0
class PurchaseSelectionStep(BaseWizardStep):
    gladefile = 'PurchaseSelectionStep'

    def __init__(self, wizard, store):
        self._next_step = None
        BaseWizardStep.__init__(self, store, wizard)
        self.setup_slaves()

    def _create_search(self):
        self.search = SearchSlave(self._get_columns(),
                                  restore_name=self.__class__.__name__,
                                  store=self.store,
                                  search_spec=PurchaseOrderView)
        self.search.enable_advanced_search()
        self.attach_slave('searchbar_holder', self.search)
        executer = self.search.get_query_executer()
        executer.add_query_callback(self.get_extra_query)
        self._create_filters()
        self.search.result_view.set_selection_mode(gtk.SELECTION_MULTIPLE)
        self.search.result_view.connect('selection-changed',
                                        self._on_results__selection_changed)
        self.search.result_view.connect('row-activated',
                                        self._on_results__row_activated)
        self.search.focus_search_entry()

    def _create_filters(self):
        self.search.set_text_field_columns(['supplier_name', 'identifier_str'])

    def get_extra_query(self, states):
        query = PurchaseOrderView.status == PurchaseOrder.ORDER_CONFIRMED

        # Dont let the user receive purchases from other branches when working
        # in synchronized mode
        if api.sysparam.get_bool('SYNCHRONIZED_MODE'):
            branch = api.get_current_branch(self.store)
            query = And(query,
                        PurchaseOrderView.branch_id == branch.id)
        return query

    def _get_columns(self):
        return [IdentifierColumn('identifier', sorted=True),
                SearchColumn('open_date', title=_('Date Started'),
                             data_type=datetime.date, width=100),
                SearchColumn('expected_receival_date', data_type=datetime.date,
                             title=_('Expected Receival'), visible=False),
                SearchColumn('supplier_name', title=_('Supplier'),
                             data_type=str, searchable=True, width=130,
                             expand=True),
                SearchColumn('ordered_quantity', title=_('Qty Ordered'),
                             data_type=Decimal, width=110,
                             format_func=format_quantity),
                SearchColumn('received_quantity', title=_('Qty Received'),
                             data_type=Decimal, width=145,
                             format_func=format_quantity),
                SearchColumn('total', title=_('Order Total'),
                             data_type=currency, width=120)]

    def _update_view(self):
        selected_rows = self.search.result_view.get_selected_rows()
        can_continue = len(set((v.supplier_id, v.branch_id) for v in selected_rows)) == 1
        self.wizard.refresh_next(can_continue)
        self.details_button.set_sensitive(len(selected_rows) == 1)

    #
    # WizardStep hooks
    #

    def post_init(self):
        self._update_view()
        self.force_validation()

    def next_step(self):
        self.search.save_columns()
        selected_rows = self.search.result_view.get_selected_rows()

        return ReceivingOrderItemStep(self.store, self.wizard, self,
                                      selected_rows)

    def has_previous_step(self):
        return False

    def setup_slaves(self):
        self._create_search()

    #
    # Kiwi callbacks
    #

    def _on_results__selection_changed(self, results, purchase_order_view):
        self.force_validation()
        self._update_view()

    def _on_results__row_activated(self, results, purchase_order_view):
        run_dialog(PurchaseDetailsDialog, self.wizard, self.store,
                   model=purchase_order_view.purchase)

    def on_details_button__clicked(self, *args):
        selected = self.search.results.get_selected_rows()[0]
        if not selected:
            raise ValueError('You should have one order selected '
                             'at this point, got nothing')
        run_dialog(PurchaseDetailsDialog, self.wizard, self.store,
                   model=selected.purchase)
Ejemplo n.º 26
0
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)
    """
    main_label_text = ''

    #: Title that will appear in the window, for instance 'Product Search'
    title = ''

    # The table type which we will query on to get the objects.
    search_spec = None

    #: The label that will be used for the main filter in this dialog
    search_label = None

    #: Selection mode to use (if its possible to select more than one row)
    selection_mode = gtk.SELECTION_BROWSE

    #: Default size for this dialog
    size = ()

    #: If the advanced search is enabled or disabled. When ``True`` we will
    #: instrospect the columns returned by :meth:`get_columns`,and use those
    #: that are subclasses of :class:`stoqlib.gui.columns.SearchColumn` to add
    #: as options for the user to filter the results.
    advanced_search = True

    tree = False

    def __init__(self,
                 store,
                 search_spec=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_spec:
        :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_spec = search_spec or self.search_spec
        if not self.search_spec:
            raise ValueError("%r needs a search table" % self)
        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.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()
        if self.search_label:
            self.set_searchbar_label(self.search_label)

    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 = SearchSlave(
            self.get_columns(),
            tree=self.tree,
            restore_name=self.__class__.__name__,
            store=self.store,
            search_spec=self.search_spec,
        )
        if self.advanced_search:
            self.search.enable_advanced_search()
        self.attach_slave('main', self.search)
        self.header.hide()

        self.results = self.search.result_view
        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)

    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

    def set_details_button_sensitive(self, value):
        self._details_slave.details_button.set_sensitive(value)

    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()

    # FIXME: -> remove/use

    # TODO: Check if we can remove
    def set_searchbar_label(self, label):
        search_filter = self.search.get_primary_filter()
        search_filter.set_label(label)

    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_text_field_columns(self, columns):
        """See :class:`SearchSlave.set_text_field_columns`
        """
        self.search.set_text_field_columns(columns)

    def disable_search_entry(self):
        """See :class:`SearchSlave.disable_search_entry`
        """
        self.search.disable_search_entry()

    def add_filter(self,
                   search_filter,
                   position=SearchFilterPosition.BOTTOM,
                   columns=None,
                   callback=None):
        """See :class:`SearchSlave.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
Ejemplo n.º 27
0
class ReceivingSelectionStep(BaseWizardStep):
    gladefile = 'PurchaseSelectionStep'

    def __init__(self, wizard, store):
        self._next_step = None
        BaseWizardStep.__init__(self, store, wizard)
        self.setup_slaves()

    def _create_search(self):
        self.search = SearchSlave(self._get_columns(),
                                  restore_name=self.__class__.__name__,
                                  store=self.store,
                                  search_spec=PurchaseReceivingView)
        self.search.enable_advanced_search()
        self.attach_slave('searchbar_holder', self.search)
        executer = self.search.get_query_executer()
        executer.add_query_callback(self.get_extra_query)
        self._create_filters()
        self.search.result_view.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
        self.search.result_view.connect('selection-changed',
                                        self._on_results__selection_changed)
        self.search.result_view.connect('row-activated',
                                        self._on_results__row_activated)
        self.search.focus_search_entry()

    def _create_filters(self):
        self.search.set_text_field_columns(['supplier_name', 'purchase_identifier'])

    def get_extra_query(self, states):
        query = And(Eq(PurchaseReceivingView.purchase_group, None),
                    Eq(PurchaseReceivingView.receiving_invoice, None))

        # Dont let the user receive purchases from other branches when working
        # in synchronized mode
        if (api.sysparam.get_bool('SYNCHRONIZED_MODE') and not
                api.can_see_all_branches()):
            branch = api.get_current_branch(self.store)
            query = And(query, PurchaseReceivingView.branch_id == branch.id)
        return query

    def _get_columns(self):
        return [IdentifierColumn('identifier', _('Receiving #'), width=140),
                IdentifierColumn('purchase_identifier', _('Purchase #'), width=110),
                SearchColumn('packing_number', title=_('Packing Number'),
                             data_type=str, visible=False),
                SearchColumn('receival_date', _('Receival date'), expand=True,
                             data_type=datetime.date, sorted=True, width=110),
                SearchColumn('supplier_name', _('Supplier'), data_type=str,
                             expand=True),
                SearchColumn('responsible_name', _('Responsible'),
                             data_type=str, visible=False, expand=True),
                SearchColumn('purchase_responsible_name', _('Purchaser'),
                             data_type=str, visible=False, expand=True),
                SearchColumn('invoice_number', _('Invoice #'), data_type=int,
                             width=80),
                Column('subtotal', title=_('Products total'),
                       data_type=currency, width=150)]

    def _update_view(self):
        selected_rows = self.search.result_view.get_selected_rows()
        can_continue = len(set((v.supplier_id, v.branch_id) for v in selected_rows)) == 1
        self.wizard.refresh_next(can_continue)
        self.details_button.set_sensitive(len(selected_rows) == 1)

    #
    # WizardStep hooks
    #

    def post_init(self):
        self._update_view()
        self.force_validation()

    def next_step(self):
        self.search.save_columns()
        selected_rows = self.search.result_view.get_selected_rows()

        return ProductsCostCheckStep(self.wizard, self, self.store, selected_rows)

    def has_previous_step(self):
        return False

    def setup_slaves(self):
        self._create_search()

    #
    # Kiwi callbacks
    #

    def _on_results__selection_changed(self, results, purchase_order_view):
        self.force_validation()
        self._update_view()

    def _on_results__row_activated(self, results, receiving_order_view):
        run_dialog(ReceivingOrderDetailsDialog, self.wizard, self.store,
                   model=receiving_order_view.order)

    def on_details_button__clicked(self, *args):
        selected = self.search.results.get_selected_rows()[0]
        if not selected:
            raise ValueError('You should have one order selected '
                             'at this point, got nothing')
        run_dialog(ReceivingOrderDetailsDialog, self.wizard, self.store,
                   model=selected.order)
Ejemplo n.º 28
0
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)
    """
    main_label_text = ''

    #: Title that will appear in the window, for instance 'Product Search'
    title = ''

    # The table type which we will query on to get the objects.
    search_spec = None

    #: The label that will be used for the main filter in this dialog
    search_label = None

    #: Selection mode to use (if its possible to select more than one row)
    selection_mode = gtk.SELECTION_BROWSE

    #: Default size for this dialog
    size = ()

    #: If the advanced search is enabled or disabled. When ``True`` we will
    #: instrospect the columns returned by :meth:`get_columns`,and use those
    #: that are subclasses of :class:`stoqlib.gui.search.searchcolumns.SearchColumn`
    #: to add as options for the user to filter the results.
    advanced_search = True

    #: the report class used to print a report for the results.
    #: If ``None``, the print button will not even be created
    report_class = None

    #: If the results should use an objecttree instead of objectlist. The result
    #: objects should have a get_parent method if this is set.
    tree = False

    #: Provide your own result_view_class for the search results.
    #: See stoqlib.gui.search.searchresultview.SearchResultTreeView for more
    #: information on what should be implemented
    result_view_class = None

    #: If we should use the (experimental) fast iter feature of the result set.
    #: See stoqlib.database.runtime for more information
    fast_iter = False

    #: If defined, should be a list of properties that will be filtred by the
    #: default entry
    text_field_columns = None

    #: If defined, this should be a column from some table that refrences a
    #: branch, and a filter will be added for this column
    branch_filter_column = None

    #: If False, the number of results will respect the parameter
    #: MAX_SEARCH_RESULTS. When True, there will be no limit and everything will
    #: be displayed
    unlimited_results = False

    def __init__(self,
                 store,
                 search_spec=None,
                 hide_footer=True,
                 title='',
                 selection_mode=None,
                 double_click_confirm=False,
                 initial_string=''):
        """
        A base class for search dialog inheritance

        :param store: a store
        :param search_spec:
        :param hide_footer:
        :param title:
        :param selection_mode:
        :param double_click_confirm: If double click a item in the list should
          automatically confirm
        :param initial_string: the string that should be initially filtered
        """

        self.store = store
        self.search_spec = search_spec or self.search_spec
        if not self.search_spec:
            raise ValueError("%r needs a search table" % self)
        self.selection_mode = self._setup_selection_mode(selection_mode)
        self.summary_label = None
        self.double_click_confirm = double_click_confirm
        self.csv_button = None
        self.initial_string = initial_string

        BasicDialog.__init__(self,
                             hide_footer=hide_footer,
                             main_label_text=self.main_label_text,
                             title=title or self.title,
                             size=self.size)

        self.enable_window_controls()
        self.disable_ok()
        self.set_ok_label(_('Se_lect Items'))
        self._setup_search()
        self._setup_details_slave()

        self._create_default_filters()
        self.create_filters()
        self.setup_widgets()
        if self.search_label:
            self.set_searchbar_label(self.search_label)

        if self.initial_string:
            search_filter = self.search.get_primary_filter()
            search_filter.set_state(self.initial_string)
            self.search.refresh()
            search_filter.entry.grab_focus()

    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.columns = self.get_columns()
        SearchDialogSetupSearchEvent.emit(self)
        self.search = SearchSlave(self.columns,
                                  tree=self.tree,
                                  restore_name=self.__class__.__name__,
                                  store=self.store,
                                  search_spec=self.search_spec,
                                  fast_iter=self.fast_iter,
                                  result_view_class=self.result_view_class)
        if self.advanced_search:
            self.search.enable_advanced_search()
        self.attach_slave('main', self.search)
        self.header.hide()

        self.results = self.search.result_view
        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)

    def _setup_details_slave(self):
        # FIXME: Gross hack
        has_details_btn = hasattr(self, 'on_details_button_clicked')
        has_print_btn = self.report_class is not None
        self.results.connect('has-rows', self._has_rows)
        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)
        else:
            self._details_slave.print_button.hide()

    def _create_default_filters(self):
        """Creates default filters

        This will create filters based on attributes defined on the class.
        """
        if self.text_field_columns is not None:
            self.set_text_field_columns(self.text_field_columns)

        if self.branch_filter_column is not None:
            self.branch_filter = self.create_branch_filter(
                column=self.branch_filter_column)

        if self.unlimited_results:
            self.search.get_query_executer().set_limit(-1)

    #
    # 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.get_resource_filename('stoq', '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_END)
        self.action_area.pack_start(button, False, False, 6)
        self.action_area.set_child_secondary(button, True)
        return button

    def add_csv_button(self, name, prefix):
        self._csv_name = name
        self._csv_prefix = prefix
        self.csv_button = self.add_button(label=_("Export to spreadsheet..."))
        self.csv_button.connect('clicked', self._on_export_csv_button__clicked)
        self.csv_button.show()
        self.csv_button.set_sensitive(False)

    def set_details_button_sensitive(self, value):
        self._details_slave.details_button.set_sensitive(value)

    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 print_report(self):
        print_report(self.report_class,
                     self.results,
                     list(self.results),
                     filters=self.search.get_search_filters())

    def birthday_search(self, state):
        """ Returns a birthday query suitable for search filters.
            This should be assigned on search_column when you want to filter for
            birth day. e.g.:
            SearchColumn('birth_date', search_column=self.birthday_search)
        """
        if isinstance(state, DateQueryState):
            if state.date:
                return Individual.get_birthday_query(state.date)
        elif isinstance(state, DateIntervalQueryState):
            if state.start and state.end:
                return Individual.get_birthday_query(state.start, state.end)
        else:
            raise AssertionError

    # FIXME: -> remove/use

    # TODO: Check if we can remove
    def set_searchbar_label(self, label):
        search_filter = self.search.get_primary_filter()
        search_filter.set_label(label)

    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_text_field_columns(self, columns):
        """See :class:`SearchSlave.set_text_field_columns`
        """
        self.search.set_text_field_columns(columns)

    def disable_search_entry(self):
        """See :class:`SearchSlave.disable_search_entry`
        """
        self.search.disable_search_entry()

    def add_filter(self,
                   search_filter,
                   position=SearchFilterPosition.BOTTOM,
                   columns=None,
                   callback=None):
        """See :class:`SearchSlave.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()

    def add_extension(self, extension):
        """Adds the extention to this search.

        See :class:`stoqlib.gui.search.searchextention.SearchExtention for more
        information
        """
        extension.attach(self)

    def add_columns(self, columns):
        """Add some columns to the default ones.

        Note that this method must be called during the setup of this search,
        which right now is only possible for those who capture the
        `<stoqlib.gui.events.SearchDialogSetupSearchEvent>`
        """
        self.columns.extend(columns)

    #
    # Filters
    #

    def create_branch_filter(self, label=None, column=None):
        return self.search.create_branch_filter(label, column)

    def create_sellable_filter(self, label=None):
        from stoqlib.domain.sellable import Sellable
        items = [(desc, status) for status, desc in Sellable.statuses.items()]
        items.insert(0, (_(u"Any"), None))

        if label is None:
            label = _('With status:')
        sellable_filter = ComboSearchFilter(label, items)
        # Select status available by default
        sellable_filter.select(Sellable.STATUS_AVAILABLE)

        return sellable_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

    def create_salesperson_filter(self, label=None):
        from stoqlib.domain.person import SalesPerson
        items = SalesPerson.get_active_items(self.store)
        items.insert(0, (_("Any"), None))

        if not label:
            label = _('Salesperson:')
        return ComboSearchFilter(label, items)

    #
    # 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):
        if self._details_slave:
            self.set_print_button_sensitive(obj)

        if self.csv_button:
            self.csv_button.set_sensitive(bool(obj))

    def _on_export_csv_button__clicked(self, widget):
        if not self.unlimited_results:
            executer = self.search.get_query_executer()
            data = executer.search(limit=-1)
        else:
            # The results are already unlimited, let the exporter get the data
            # from the objectlist
            data = None

        sse = SpreadSheetExporter()
        sse.export(object_list=self.results,
                   data=data,
                   name=self._csv_name,
                   filename_prefix=self._csv_prefix)

    def _on_print_button__clicked(self, button):
        self.print_report()

    #
    # Hooks
    #

    def create_filters(self):
        pass

    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
Ejemplo n.º 29
0
class ShellApp(GladeDelegate):
    """Base class for shell applications.

    The main use is to interact with a shell window and reduce
    duplication between other applications.
    """

    domain = 'stoq'

    #: This attribute is used when generating titles for applications.
    #: It's also useful if we get a list of available applications with
    #: the application names translated. This list is going to be used when
    #: creating new user profiles.
    app_title = None

    #: name of the application, 'pos', 'payable', etc
    app_name = None

    #: If this application has a search like interface
    search = None

    #: This dictionary holds information about required permissions to access
    #: certain actions. Keys should be the name of the action (for instance
    #: SearchEmployess), and the value should be a tuple with the permission key
    #: (domain object or action identifier) and the required permission. In this
    #: case: ('Employee', perm.PERM_SEARCH). See <stoqlib.lib.permissions>
    action_permissions = {}

    #: The spec for store.find() to perform the search on
    search_spec = None

    #: Label left of the search entry
    search_label = _('Search:')

    #: the report class for printing the object list embedded on app.
    report_table = None

    def __init__(self, window, store=None):
        if store is None:
            store = api.get_default_store()
        self.store = store
        self.window = window

        self._sensitive_group = dict()
        self.help_ui = None
        self.uimanager = self.window.uimanager

        # FIXME: These two should probably post-__init__, but
        #        that breaks date_label in the calender app
        self._create_search()
        self.create_actions()
        GladeDelegate.__init__(self,
                               gladefile=self.gladefile,
                               toplevel_name=self.toplevel_name)
        self._attach_search()
        self.create_ui()

    def _create_search(self):
        if self.search_spec is None:
            return
        self.columns = self.get_columns()
        ApplicationSetupSearchEvent.emit(self)
        self.search = SearchSlave(self.columns,
                                  store=self.store,
                                  restore_name=self.__class__.__name__,
                                  search_spec=self.search_spec)

    def _attach_search(self):
        if self.search_spec is None:
            return
        self.search.enable_advanced_search()
        self.attach_slave('search_holder', self.search)
        search_filter = self.search.get_primary_filter()
        search_filter.set_label(self.search_label)
        self.create_filters()
        self.search.restore_filter_settings('app-ui', self.app_name)
        self.search.focus_search_entry()

        # FIXME: Remove and use search directly instead of the result view
        self.results = self.search.result_view

    def _display_open_inventory_message(self):
        msg = _(u'There is an inventory process open at the moment.\n'
                'While that inventory is open, you will be unable to do '
                'operations that modify your stock.')
        self.inventory_bar = self.window.add_info_bar(Gtk.MessageType.WARNING,
                                                      msg)

    #
    # Overridables
    #

    def create_actions(self):
        """This is called before the BaseWindow constructor, so we
        can create actions that can be autoconnected.
        The widgets and actions loaded from builder files are not set
        yet"""

    def create_ui(self):
        """This is called when the UI such as GtkWidgets should be
        created. Glade widgets are now created and can be accessed
        in the instance.
        """

    def activate(self, refresh=True):
        """This is when you switch to an application.

        You should setup widget sensitivity here and refresh lists etc

        :param refresh: if we should refresh the search
        """

    def setup_focus(self):
        """Define this method on child when it's needed.
        This is for calling grab_focus(), it's called after the window
        is shown. focus chains should be created in create_ui()"""

    def get_title(self):
        # This method must be redefined in child when it's needed
        branch = api.get_current_branch(self.store)
        return _('[%s] - %s') % (branch.get_description(), self.app_title)

    def can_change_application(self):
        """Define if we can change the current application or not.

        :returns: True if we can change the application, False otherwise.
        """
        return True

    def can_close_application(self):
        """Define if we can close the current application or not.

        :returns: True if we can close the application, False otherwise.
        """
        return True

    def set_open_inventory(self):
        """ Subclasses should overide this if they call
        :obj:`.check_open_inventory`.

        This method will be called it there is an open inventory, so the
        application can disable some funcionalities
        """
        raise NotImplementedError

    def new_activate(self):
        """Called when the New toolbar item is activated"""
        raise NotImplementedError

    def search_activate(self):
        """Called when the Search toolbar item is activated"""
        raise NotImplementedError

    def print_activate(self):
        """Called when the Print toolbar item is activated"""
        if self.search_spec is None:
            raise NotImplementedError

        if self.results.get_selection_mode() == Gtk.SelectionMode.MULTIPLE:
            results = self.results.get_selected_rows() or list(
                self.search.get_last_results())
        else:
            # There are not multiple selection.
            # We should print the entire list.
            results = list(self.search.get_last_results())
        self.print_report(self.report_table, self.results, results)

    def export_spreadsheet_activate(self):
        """Called when the Export menu item is activated"""
        if self.search_spec is None:
            raise NotImplementedError

        model = self.results.get_model()
        if isinstance(model, LazyObjectModel):
            model.load_items_from_results(0, model._count)

        sse = SpreadSheetExporter()
        sse.export(object_list=self.results,
                   name=self.app_name,
                   filename_prefix=self.app_name)

    def create_filters(self):
        """Implement this to provide filters for the search container"""

    def search_completed(self, results, states):
        """Implement this if you want to know when a search has
        been completed.

        :param results: the search results
        :param states: search states used to construct the search query search
        """

    #
    # Public API
    #

    def add_ui_actions(self,
                       ui_string,
                       actions,
                       name='Actions',
                       action_type='normal',
                       filename=None):
        return self.window.add_ui_actions(ui_string=ui_string,
                                          actions=actions,
                                          name=name,
                                          action_type=action_type,
                                          filename=filename,
                                          instance=self)

    def add_tool_menu_actions(self, actions):
        return self.window.add_tool_menu_actions(actions=actions)

    def add_columns(self, columns):
        """Add some columns to the default ones.

        Note that this method must be called during the setup of this search,
        which right now is only possible for those who capture the
        `<stoqlib.gui.events.ApplicationSetupSearchEvent>`
        """
        self.columns.extend(columns)

    def set_help_section(self, label, section):
        self.window.set_help_section(label=label, section=section)

    def get_statusbar_message_area(self):
        return self.window.statusbar.message_area

    def print_report(self, report_class, *args, **kwargs):
        filters = self.search.get_search_filters()
        if filters:
            kwargs['filters'] = filters

        print_report(report_class, *args, **kwargs)

    def set_sensitive(self, widgets, value):
        """Set the *widgets* sensitivity based on *value*

        If a sensitive group was registered for any widget,
        it's validation function will be tested and, if ``False``
        is returned, it will be set insensitive, ignoring *value*

        :param widgets: a list of widgets
        :param value: either `True` or `False`
        """
        # FIXME: Maybe this should ne done on kiwi?
        for widget in widgets:
            sensitive = value

            for validator in self._sensitive_group.get(widget, []):
                if not validator[0](*validator[1]):
                    sensitive = False
                    break

            widget.set_sensitive(sensitive)

    def register_sensitive_group(self, widgets, validation_func, *args):
        """Register widgets on a sensitive group.

        Everytime :obj:`.set_sensitive()` is called, if there is any
        validation function for a given widget on sensitive group,
        then that will be used to decide if it gets sensitive or
        insensitive.

        :param widgets: a list of widgets
        :param validation_func: a function for validation. It should
            return either ``True`` or ``False``.
        :param args: args that will be passed to *validation_func*
        """
        assert callable(validation_func)

        for widget in widgets:
            validators = self._sensitive_group.setdefault(widget, set())
            validators.add((validation_func, args))

    def run_dialog(self, dialog_class, *args, **kwargs):
        """ Encapsuled method for running dialogs. """
        return run_dialog(dialog_class, self, *args, **kwargs)

    @cached_function()
    def has_open_inventory(self):
        return Inventory.has_open(self.store,
                                  api.get_current_branch(self.store))

    def check_open_inventory(self):
        """Checks if there is an open inventory.

        In the case there is one, will call set_open_inventory (subclasses
        should implement it).

        Returns True if there is an open inventory. False otherwise
        """
        inventory_bar = getattr(self, 'inventory_bar', None)

        if self.has_open_inventory():
            if inventory_bar:
                inventory_bar.show()
            else:
                self._display_open_inventory_message()
            self.set_open_inventory()
            return True
        elif inventory_bar:
            inventory_bar.hide()
            return False

    # FIXME: Most of these should be removed and access the search API
    #        directly, eg, self.search.clear() etc

    def add_filter(self,
                   search_filter,
                   position=SearchFilterPosition.BOTTOM,
                   columns=None,
                   callback=None):
        """
        See :class:`SearchSlave.add_filter`
        """
        self.search.add_filter(search_filter, position, columns, callback)

    def set_text_field_columns(self, columns):
        """
        See :class:`SearchSlave.set_text_field_columns`
        """
        self.search.set_text_field_columns(columns)

    def create_branch_filter(self, label=None, column=None):
        branch_filter = self.search.create_branch_filter(label, column)
        # If there is only one item in the combo, lets hide it.
        if len(branch_filter.combo) == 1:
            branch_filter.hide()
        return branch_filter

    def refresh(self, rollback=True):
        """
        See :class:`stoqlib.gui.search.searchslave.SearchSlave.refresh`
        """
        # Since the store here is actually a transaction and the items
        # on it can be changed from another station, do a rollback so
        # the items get reloaded, avoiding cache problems
        # Note that this gets mocked on tests to not do the rollback
        if rollback:
            self.store.rollback(close=False)
        self.search.refresh()

    def clear(self):
        """
        See :class:`stoqlib.gui.search.searchslave.SearchSlave.clear`
        """
        self.search.clear()

    def select_result(self, result):
        """Select the object in the result list

        If the object is not in the list (filtered out, for instance), no error
        is thrown and nothing is selected
        """
        try:
            self.results.select(result)
        except ValueError:
            pass

    #
    # Callbacks
    #

    def on_search__search_completed(self, search, results, states):
        self.search_completed(results, states)

        has_results = len(results)
        for widget in [self.window.Print, self.window.ExportSpreadSheet]:
            widget.set_sensitive(has_results)
        self.search.save_filter_settings('app-ui', self.app_name)
Ejemplo n.º 30
0
class QuoteGroupSelectionStep(BaseWizardStep):
    gladefile = 'QuoteGroupSelectionStep'

    def __init__(self, wizard, store):
        self._next_step = None
        BaseWizardStep.__init__(self, store, wizard)
        self._setup_slaves()

    def _setup_slaves(self):
        self.search = SearchSlave(self._get_columns(),
                                  restore_name=self.__class__.__name__,
                                  search_spec=QuotationView,
                                  store=self.store)
        self.attach_slave('search_group_holder', self.search)

        self.search.set_text_field_columns(['supplier_name', 'identifier_str'])
        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 _get_columns(self):
        return [IdentifierColumn('identifier', title=_("Quote #"), sorted=True),
                IdentifierColumn('group_identifier', title=_('Group #')),
                Column('supplier_name', title=_('Supplier'), data_type=str,
                       width=300),
                Column('open_date', title=_('Open date'),
                       data_type=datetime.date),
                Column('deadline', title=_('Deadline'),
                       data_type=datetime.date)]

    def _can_purchase(self, item):
        return item.cost > currency(0) and item.quantity > Decimal(0)

    def _can_order(self, quotation):
        if quotation is None:
            return False

        for item in quotation.purchase.get_items():
            if not self._can_purchase(item):
                return False
        return True

    def _update_view(self):
        selected = self.search.results.get_selected()
        has_selected = selected is not None
        self.edit_button.set_sensitive(has_selected)
        self.remove_button.set_sensitive(has_selected)
        self.wizard.refresh_next(self._can_order(selected))

    def _run_quote_editor(self):
        store = api.new_store()
        selected = store.fetch(self.search.results.get_selected().purchase)
        retval = run_dialog(QuoteFillingDialog, self.wizard, selected, store)
        store.confirm(retval)
        store.close()
        self._update_view()

    def _remove_quote(self):
        q = self.search.results.get_selected().quotation
        msg = _('Are you sure you want to remove "%s" ?') % q.get_description()
        if not yesno(msg, gtk.RESPONSE_NO,
                     _("Remove quote"), _("Don't remove")):
            return

        store = api.new_store()
        group = store.fetch(q.group)
        quote = store.fetch(q)
        group.remove_item(quote)
        # there is no reason to keep the group if there's no more quotes
        if group.get_items().count() == 0:
            store.remove(group)
        store.confirm(True)
        store.close()
        self.search.refresh()

    #
    # WizardStep hooks
    #

    def next_step(self):
        self.search.save_columns()
        selected = self.search.results.get_selected()
        if selected is None:
            return

        return QuoteGroupItemsSelectionStep(self.wizard, self.store,
                                            selected.group, self)

    #
    # Callbacks
    #

    def _on_searchlist__selection_changed(self, widget, item):
        self._update_view()

    def _on_searchlist__row_activated(self, widget, item):
        self._run_quote_editor()

    def on_edit_button__clicked(self, widget):
        self._run_quote_editor()

    def on_remove_button__clicked(self, widget):
        self._remove_quote()
Ejemplo n.º 31
0
class PurchaseSelectionStep(BaseWizardStep):
    gladefile = 'PurchaseSelectionStep'

    def __init__(self, wizard, store):
        self._next_step = None
        BaseWizardStep.__init__(self, store, wizard)
        self.setup_slaves()

    def _create_search(self):
        self.search = SearchSlave(self._get_columns(),
                                  restore_name=self.__class__.__name__,
                                  store=self.store,
                                  search_spec=PurchaseOrderView)
        self.search.enable_advanced_search()
        self.attach_slave('searchbar_holder', self.search)
        executer = self.search.get_query_executer()
        executer.add_query_callback(self.get_extra_query)
        self._create_filters()
        self.search.result_view.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
        self.search.result_view.connect('selection-changed',
                                        self._on_results__selection_changed)
        self.search.result_view.connect('row-activated',
                                        self._on_results__row_activated)
        self.search.focus_search_entry()

    def _create_filters(self):
        self.search.set_text_field_columns(['supplier_name', 'identifier_str'])

    def get_extra_query(self, states):
        query = PurchaseOrderView.status == PurchaseOrder.ORDER_CONFIRMED

        # Dont let the user receive purchases from other branches when working
        # in synchronized mode
        if (api.sysparam.get_bool('SYNCHRONIZED_MODE')
                and not api.can_see_all_branches()):
            branch = api.get_current_branch(self.store)
            query = And(query, PurchaseOrderView.branch_id == branch.id)
        return query

    def _get_columns(self):
        return [
            IdentifierColumn('identifier', title=_('Purchase #'), sorted=True),
            SearchColumn('open_date',
                         title=_('Date Started'),
                         data_type=datetime.date,
                         width=100),
            SearchColumn('expected_receival_date',
                         data_type=datetime.date,
                         title=_('Expected Receival'),
                         visible=False),
            SearchColumn('supplier_name',
                         title=_('Supplier'),
                         data_type=str,
                         searchable=True,
                         width=130,
                         expand=True),
            SearchColumn('ordered_quantity',
                         title=_('Qty Ordered'),
                         data_type=Decimal,
                         width=110,
                         format_func=format_quantity),
            SearchColumn('received_quantity',
                         title=_('Qty Received'),
                         data_type=Decimal,
                         width=145,
                         format_func=format_quantity),
            SearchColumn('total',
                         title=_('Order Total'),
                         data_type=currency,
                         width=120)
        ]

    def _update_view(self):
        selected_rows = self.search.result_view.get_selected_rows()
        can_continue = len(
            set((v.supplier_id, v.branch_id) for v in selected_rows)) == 1
        self.wizard.refresh_next(can_continue)
        self.details_button.set_sensitive(len(selected_rows) == 1)

    #
    # WizardStep hooks
    #

    def post_init(self):
        self._update_view()
        self.force_validation()

    def next_step(self):
        self.search.save_columns()
        selected_rows = self.search.result_view.get_selected_rows()

        return ReceivingOrderItemStep(self.store, self.wizard, self,
                                      selected_rows)

    def has_previous_step(self):
        return False

    def setup_slaves(self):
        self._create_search()

    #
    # Kiwi callbacks
    #

    def _on_results__selection_changed(self, results, purchase_order_view):
        self.force_validation()
        self._update_view()

    def _on_results__row_activated(self, results, purchase_order_view):
        run_dialog(PurchaseDetailsDialog,
                   self.wizard,
                   self.store,
                   model=purchase_order_view.purchase)

    def on_details_button__clicked(self, *args):
        selected = self.search.results.get_selected_rows()[0]
        if not selected:
            raise ValueError('You should have one order selected '
                             'at this point, got nothing')
        run_dialog(PurchaseDetailsDialog,
                   self.wizard,
                   self.store,
                   model=selected.purchase)
Ejemplo n.º 32
0
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)
    """
    main_label_text = ''

    #: Title that will appear in the window, for instance 'Product Search'
    title = ''

    # The table type which we will query on to get the objects.
    search_spec = None

    #: The label that will be used for the main filter in this dialog
    search_label = None

    #: Selection mode to use (if its possible to select more than one row)
    selection_mode = gtk.SELECTION_BROWSE

    #: Default size for this dialog
    size = ()

    #: If the advanced search is enabled or disabled. When ``True`` we will
    #: instrospect the columns returned by :meth:`get_columns`,and use those
    #: that are subclasses of :class:`stoqlib.gui.search.searchcolumns.SearchColumn`
    #: to add as options for the user to filter the results.
    advanced_search = True

    #: the report class used to print a report for the results.
    #: If ``None``, the print button will not even be created
    report_class = None

    #: If the results should use an objecttree instead of objectlist. The result
    #: objects should have a get_parent method if this is set.
    tree = False

    #: Provide your own result_view_class for the search results.
    #: See stoqlib.gui.search.searchresultview.SearchResultTreeView for more
    #: information on what should be implemented
    result_view_class = None

    #: If we should use the (experimental) fast iter feature of the result set.
    #: See stoqlib.database.runtime for more information
    fast_iter = False

    #: If defined, should be a list of properties that will be filtred by the
    #: default entry
    text_field_columns = None

    #: If defined, this should be a column from some table that refrences a
    #: branch, and a filter will be added for this column
    branch_filter_column = None

    #: If False, the number of results will respect the parameter
    #: MAX_SEARCH_RESULTS. When True, there will be no limit and everything will
    #: be displayed
    unlimited_results = False

    def __init__(self, store, search_spec=None, hide_footer=True,
                 title='', selection_mode=None, double_click_confirm=False,
                 initial_string=''):
        """
        A base class for search dialog inheritance

        :param store: a store
        :param search_spec:
        :param hide_footer:
        :param title:
        :param selection_mode:
        :param double_click_confirm: If double click a item in the list should
          automatically confirm
        :param initial_string: the string that should be initially filtered
        """

        self.store = store
        self.search_spec = search_spec or self.search_spec
        if not self.search_spec:
            raise ValueError("%r needs a search table" % self)
        self.selection_mode = self._setup_selection_mode(selection_mode)
        self.summary_label = None
        self.double_click_confirm = double_click_confirm
        self.csv_button = None
        self.initial_string = initial_string

        BasicDialog.__init__(self, hide_footer=hide_footer,
                             main_label_text=self.main_label_text,
                             title=title or self.title,
                             size=self.size)

        self.enable_window_controls()
        self.disable_ok()
        self.set_ok_label(_('Se_lect Items'))
        self._setup_search()
        self._setup_details_slave()

        self._create_default_filters()
        self.create_filters()
        self.setup_widgets()
        if self.search_label:
            self.set_searchbar_label(self.search_label)

        if self.initial_string:
            search_filter = self.search.get_primary_filter()
            search_filter.set_state(self.initial_string)
            self.search.refresh()
            search_filter.entry.grab_focus()

    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.columns = self.get_columns()
        SearchDialogSetupSearchEvent.emit(self)
        self.search = SearchSlave(
            self.columns,
            tree=self.tree,
            restore_name=self.__class__.__name__,
            store=self.store,
            search_spec=self.search_spec,
            fast_iter=self.fast_iter,
            result_view_class=self.result_view_class
        )
        if self.advanced_search:
            self.search.enable_advanced_search()
        self.attach_slave('main', self.search)
        self.header.hide()

        self.results = self.search.result_view
        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)

    def _setup_details_slave(self):
        # FIXME: Gross hack
        has_details_btn = hasattr(self, 'on_details_button_clicked')
        has_print_btn = self.report_class is not None
        self.results.connect('has-rows', self._has_rows)
        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)
        else:
            self._details_slave.print_button.hide()

    def _create_default_filters(self):
        """Creates default filters

        This will create filters based on attributes defined on the class.
        """
        if self.text_field_columns is not None:
            self.set_text_field_columns(self.text_field_columns)

        if self.branch_filter_column is not None:
            self.branch_filter = self.create_branch_filter(
                column=self.branch_filter_column)

        if self.unlimited_results:
            self.search.get_query_executer().set_limit(-1)

    #
    # 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.get_resource_filename('stoq', '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_END)
        self.action_area.pack_start(button, False, False, 6)
        self.action_area.set_child_secondary(button, True)
        return button

    def add_csv_button(self, name, prefix):
        self._csv_name = name
        self._csv_prefix = prefix
        self.csv_button = self.add_button(label=_("Export to spreadsheet..."))
        self.csv_button.connect('clicked', self._on_export_csv_button__clicked)
        self.csv_button.show()
        self.csv_button.set_sensitive(False)

    def set_details_button_sensitive(self, value):
        self._details_slave.details_button.set_sensitive(value)

    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 print_report(self):
        print_report(self.report_class, self.results, list(self.results),
                     filters=self.search.get_search_filters())

    # FIXME: This should be on BasePersonSearch
    def birthday_search(self, state):
        """ Returns a birthday query suitable for search filters.
            This should be assigned on search_column when you want to filter for
            birth day. e.g.:
            SearchColumn('birth_date', search_column=self.birthday_search)
        """
        if isinstance(state, DateQueryState):
            if state.date:
                return Individual.get_birthday_query(state.date)
        elif isinstance(state, DateIntervalQueryState):
            if state.start and state.end:
                return Individual.get_birthday_query(state.start, state.end)
        else:
            raise AssertionError

    # FIXME: -> remove/use

    # TODO: Check if we can remove
    def set_searchbar_label(self, label):
        search_filter = self.search.get_primary_filter()
        search_filter.set_label(label)

    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_text_field_columns(self, columns):
        """See :class:`SearchSlave.set_text_field_columns`
        """
        self.search.set_text_field_columns(columns)

    def disable_search_entry(self):
        """See :class:`SearchSlave.disable_search_entry`
        """
        self.search.disable_search_entry()

    def add_filter(self, search_filter, position=SearchFilterPosition.BOTTOM,
                   columns=None, callback=None):
        """See :class:`SearchSlave.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()

    def add_extension(self, extension):
        """Adds the extention to this search.

        See :class:`stoqlib.gui.search.searchextention.SearchExtention for more
        information
        """
        extension.attach(self)

    def add_columns(self, columns):
        """Add some columns to the default ones.

        Note that this method must be called during the setup of this search,
        which right now is only possible for those who capture the
        `<stoqlib.gui.events.SearchDialogSetupSearchEvent>`
        """
        self.columns.extend(columns)

    #
    # Filters
    #

    def create_branch_filter(self, label=None, column=None):
        return self.search.create_branch_filter(label, column)

    def create_sellable_filter(self, label=None):
        from stoqlib.domain.sellable import Sellable
        items = [(desc, status) for status, desc in Sellable.statuses.items()]
        items.insert(0, (_(u"Any"), None))

        if label is None:
            label = _('With status:')
        sellable_filter = ComboSearchFilter(label, items)
        # Select status available by default
        sellable_filter.select(Sellable.STATUS_AVAILABLE)

        return sellable_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

    def create_salesperson_filter(self, label=None):
        from stoqlib.domain.person import SalesPerson
        items = SalesPerson.get_active_items(self.store)
        items.insert(0, (_("Any"), None))

        if not label:
            label = _('Salesperson:')
        return ComboSearchFilter(label, items)

    #
    # 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):
        if self._details_slave:
            self.set_print_button_sensitive(obj)

        if self.csv_button:
            self.csv_button.set_sensitive(bool(obj))

    def _on_export_csv_button__clicked(self, widget):
        if not self.unlimited_results:
            executer = self.search.get_query_executer()
            data = executer.search(limit=-1)
        else:
            # The results are already unlimited, let the exporter get the data
            # from the objectlist
            data = None

        sse = SpreadSheetExporter()
        sse.export(object_list=self.results,
                   data=data,
                   name=self._csv_name,
                   filename_prefix=self._csv_prefix)

    def _on_print_button__clicked(self, button):
        self.print_report()

    #
    # Hooks
    #

    def create_filters(self):
        pass

    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
Ejemplo n.º 33
0
class PurchaseSelectionStep(BaseWizardStep):
    gladefile = 'PurchaseSelectionStep'

    def __init__(self, wizard, store):
        self._next_step = None
        BaseWizardStep.__init__(self, store, wizard)
        self.setup_slaves()

    def _refresh_next(self, validation_value):
        has_selection = self.search.results.get_selected() is not None
        self.wizard.refresh_next(has_selection)

    def _create_search(self):
        self.search = SearchSlave(self._get_columns(),
                                  restore_name=self.__class__.__name__,
                                  store=self.store,
                                  search_spec=PurchaseOrderView)
        self.search.enable_advanced_search()
        self.attach_slave('searchbar_holder', self.search)
        executer = self.search.get_query_executer()
        executer.add_query_callback(self.get_extra_query)
        self._create_filters()
        self.search.results.connect('selection-changed',
                                    self._on_results__selection_changed)
        self.search.results.connect('row-activated',
                                    self._on_results__row_activated)
        self.search.focus_search_entry()

    def _create_filters(self):
        self.search.set_text_field_columns(['supplier_name', 'identifier_str'])

    def get_extra_query(self, states):
        query = PurchaseOrderView.status == PurchaseOrder.ORDER_CONFIRMED

        # Dont let the user receive purchases from other branches when working
        # in synchronized mode
        if api.sysparam.get_bool('SYNCHRONIZED_MODE'):
            branch = api.get_current_branch(self.store)
            query = And(query,
                        PurchaseOrderView.branch_id == branch.id)
        return query

    def _get_columns(self):
        return [IdentifierColumn('identifier', sorted=True),
                SearchColumn('open_date', title=_('Date Started'),
                             data_type=datetime.date, width=100),
                SearchColumn('expected_receival_date', data_type=datetime.date,
                             title=_('Expected Receival'), visible=False),
                SearchColumn('supplier_name', title=_('Supplier'),
                             data_type=str, searchable=True, width=130,
                             expand=True),
                SearchColumn('ordered_quantity', title=_('Qty Ordered'),
                             data_type=Decimal, width=110,
                             format_func=format_quantity),
                SearchColumn('received_quantity', title=_('Qty Received'),
                             data_type=Decimal, width=145,
                             format_func=format_quantity),
                SearchColumn('total', title=_('Order Total'),
                             data_type=currency, width=120)]

    def _update_view(self):
        has_selection = self.search.results.get_selected() is not None
        self.details_button.set_sensitive(has_selection)

    #
    # WizardStep hooks
    #

    def post_init(self):
        self._update_view()
        self.register_validate_function(self._refresh_next)
        self.force_validation()

    def next_step(self):
        self.search.save_columns()
        selected = self.search.results.get_selected()
        purchase = selected.purchase

        # We cannot create the model in the wizard since we haven't
        # selected a PurchaseOrder yet which ReceivingOrder depends on
        # Create the order here since this is the first place where we
        # actually have a purchase selected
        if not self.wizard.model:
            self.wizard.model = self.model = ReceivingOrder(
                responsible=api.get_current_user(self.store),
                supplier=purchase.supplier, invoice_number=None,
                branch=purchase.branch, purchase=purchase,
                store=self.store)

        # Remove all the items added previously, used if we hit back
        # at any point in the wizard.
        if self.model.purchase != purchase:
            self.model.remove_items()
            # This forces ReceivingOrderProductStep to create a new model
            self._next_step = None

        if selected:
            self.model.purchase = purchase
            self.model.branch = purchase.branch
            self.model.supplier = purchase.supplier
            self.model.transporter = purchase.transporter
        else:
            self.model.purchase = None

        # FIXME: Improve the infrastructure to avoid this local caching of
        #        Wizard steps.
        if not self._next_step:
            # Remove all the items added previously, used if we hit back
            # at any point in the wizard.
            self._next_step = ReceivingOrderItemStep(self.store, self.wizard,
                                                     self.model, self)
        return self._next_step

    def has_previous_step(self):
        return False

    def setup_slaves(self):
        self._create_search()

    #
    # Kiwi callbacks
    #

#     def on_searchbar_activate(self, slave, objs):
#         """Use this callback with SearchBar search-activate signal"""
#         self.results.add_list(objs, clear=True)
#         has_selection = self.results.get_selected() is not None
#         self.wizard.refresh_next(has_selection)

    def _on_results__selection_changed(self, results, purchase_order_view):
        self.force_validation()
        self._update_view()

    def _on_results__row_activated(self, results, purchase_order_view):
        run_dialog(PurchaseDetailsDialog, self.wizard, self.store,
                   model=purchase_order_view.purchase)

    def on_details_button__clicked(self, *args):
        selected = self.search.results.get_selected()
        if not selected:
            raise ValueError('You should have one order selected '
                             'at this point, got nothing')
        run_dialog(PurchaseDetailsDialog, self.wizard, self.store,
                   model=selected.purchase)
Ejemplo n.º 34
0
class ReceivingSelectionStep(BaseWizardStep):
    gladefile = 'PurchaseSelectionStep'

    def __init__(self, wizard, store):
        self._next_step = None
        BaseWizardStep.__init__(self, store, wizard)
        self.setup_slaves()

    def _create_search(self):
        self.search = SearchSlave(self._get_columns(),
                                  restore_name=self.__class__.__name__,
                                  store=self.store,
                                  search_spec=PurchaseReceivingView)
        self.search.enable_advanced_search()
        self.attach_slave('searchbar_holder', self.search)
        executer = self.search.get_query_executer()
        executer.add_query_callback(self.get_extra_query)
        self._create_filters()
        self.search.result_view.set_selection_mode(Gtk.SelectionMode.MULTIPLE)
        self.search.result_view.connect('selection-changed',
                                        self._on_results__selection_changed)
        self.search.result_view.connect('row-activated',
                                        self._on_results__row_activated)
        self.search.focus_search_entry()

    def _create_filters(self):
        self.search.set_text_field_columns(['supplier_name', 'purchase_identifier'])

    def get_extra_query(self, states):
        query = And(Eq(PurchaseReceivingView.purchase_group, None),
                    Eq(PurchaseReceivingView.receiving_invoice, None))

        # Dont let the user receive purchases from other branches when working
        # in synchronized mode
        if (api.sysparam.get_bool('SYNCHRONIZED_MODE') and not
                api.can_see_all_branches()):
            branch = api.get_current_branch(self.store)
            query = And(query, PurchaseReceivingView.branch_id == branch.id)
        return query

    def _get_columns(self):
        return [IdentifierColumn('identifier', _('Receiving #'), width=140),
                IdentifierColumn('purchase_identifier', _('Purchase #'), width=110),
                SearchColumn('packing_number', title=_('Packing Number'),
                             data_type=str, visible=False),
                SearchColumn('receival_date', _('Receival date'), expand=True,
                             data_type=datetime.date, sorted=True, width=110),
                SearchColumn('supplier_name', _('Supplier'), data_type=str,
                             expand=True),
                SearchColumn('responsible_name', _('Responsible'),
                             data_type=str, visible=False, expand=True),
                SearchColumn('purchase_responsible_name', _('Purchaser'),
                             data_type=str, visible=False, expand=True),
                SearchColumn('invoice_number', _('Invoice #'), data_type=int,
                             width=80),
                Column('subtotal', title=_('Products total'),
                       data_type=currency, width=150)]

    def _update_view(self):
        selected_rows = self.search.result_view.get_selected_rows()
        can_continue = len(set((v.supplier_id, v.branch_id) for v in selected_rows)) == 1
        self.wizard.refresh_next(can_continue)
        self.details_button.set_sensitive(len(selected_rows) == 1)

    #
    # WizardStep hooks
    #

    def post_init(self):
        self._update_view()
        self.force_validation()

    def next_step(self):
        self.search.save_columns()
        selected_rows = self.search.result_view.get_selected_rows()

        return ProductsCostCheckStep(self.wizard, self, self.store, selected_rows)

    def has_previous_step(self):
        return False

    def setup_slaves(self):
        self._create_search()

    #
    # Kiwi callbacks
    #

    def _on_results__selection_changed(self, results, purchase_order_view):
        self.force_validation()
        self._update_view()

    def _on_results__row_activated(self, results, receiving_order_view):
        run_dialog(ReceivingOrderDetailsDialog, self.wizard, self.store,
                   model=receiving_order_view.order)

    def on_details_button__clicked(self, *args):
        selected = self.search.results.get_selected_rows()[0]
        if not selected:
            raise ValueError('You should have one order selected '
                             'at this point, got nothing')
        run_dialog(ReceivingOrderDetailsDialog, self.wizard, self.store,
                   model=selected.order)
Ejemplo n.º 35
0
class QuoteGroupSelectionStep(BaseWizardStep):
    gladefile = 'QuoteGroupSelectionStep'

    def __init__(self, wizard, store):
        self._next_step = None
        BaseWizardStep.__init__(self, store, wizard)
        self._setup_slaves()

    def _setup_slaves(self):
        self.search = SearchSlave(self._get_columns(),
                                  restore_name=self.__class__.__name__,
                                  search_spec=QuotationView,
                                  store=self.store)
        self.attach_slave('search_group_holder', self.search)

        self.search.set_text_field_columns(['supplier_name', 'identifier_str'])
        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 _get_columns(self):
        return [IdentifierColumn('identifier', title=_("Quote #"), sorted=True),
                IdentifierColumn('group_identifier', title=_('Group #')),
                Column('supplier_name', title=_('Supplier'), data_type=str,
                       width=300),
                Column('open_date', title=_('Open date'),
                       data_type=datetime.date),
                Column('deadline', title=_('Deadline'),
                       data_type=datetime.date)]

    def _can_purchase(self, item):
        return item.cost > currency(0) and item.quantity > Decimal(0)

    def _can_order(self, quotation):
        if quotation is None:
            return False

        for item in quotation.purchase.get_items():
            if not self._can_purchase(item):
                return False
        return True

    def _update_view(self):
        selected = self.search.results.get_selected()
        has_selected = selected is not None
        self.edit_button.set_sensitive(has_selected)
        self.remove_button.set_sensitive(has_selected)
        self.wizard.refresh_next(self._can_order(selected))

    def _run_quote_editor(self):
        store = api.new_store()
        selected = store.fetch(self.search.results.get_selected().purchase)
        retval = run_dialog(QuoteFillingDialog, self.wizard, selected, store)
        store.confirm(retval)
        store.close()
        self._update_view()

    def _remove_quote(self):
        q = self.search.results.get_selected().quotation
        msg = _('Are you sure you want to remove "%s" ?') % q.get_description()
        if not yesno(msg, Gtk.ResponseType.NO,
                     _("Remove quote"), _("Don't remove")):
            return

        store = api.new_store()
        group = store.fetch(q.group)
        quote = store.fetch(q)
        group.remove_item(quote)
        # there is no reason to keep the group if there's no more quotes
        if group.get_items().count() == 0:
            store.remove(group)
        store.confirm(True)
        store.close()
        self.search.refresh()

    #
    # WizardStep hooks
    #

    def next_step(self):
        self.search.save_columns()
        selected = self.search.results.get_selected()
        if selected is None:
            return

        return QuoteGroupItemsSelectionStep(self.wizard, self.store,
                                            selected.group, self)

    #
    # Callbacks
    #

    def _on_searchlist__selection_changed(self, widget, item):
        self._update_view()

    def _on_searchlist__row_activated(self, widget, item):
        self._run_quote_editor()

    def on_edit_button__clicked(self, widget):
        self._run_quote_editor()

    def on_remove_button__clicked(self, widget):
        self._remove_quote()
Ejemplo n.º 36
0
class ShellApp(GladeDelegate):
    """Base class for shell applications.

    The main use is to interact with a shell window and reduce
    duplication between other applications.
    """

    #: This attribute is used when generating titles for applications.
    #: It's also useful if we get a list of available applications with
    #: the application names translated. This list is going to be used when
    #: creating new user profiles.
    app_title = None

    #: name of the application, 'pos', 'payable', etc
    app_name = None

    #: If this application has a search like interface
    search = None

    #: This dictionary holds information about required permissions to access
    #: certain actions. Keys should be the name of the action (for instance
    #: SearchEmployess), and the value should be a tuple with the permission key
    #: (domain object or action identifier) and the required permission. In this
    #: case: ('Employee', perm.PERM_SEARCH). See <stoqlib.lib.permissions>
    action_permissions = {}

    #: The table we will query on to perform the search
    search_table = None

    #: Label left of the search entry
    search_label = _('Search:')

    #: the report class for printing the object list embedded on app.
    report_table = None

    def __init__(self, window, store=None):
        if store is None:
            store = api.get_default_store()
        self.store = store
        self.window = window

        self._loading_filters = False
        self._sensitive_group = dict()
        self.help_ui = None
        self.uimanager = self.window.uimanager

        self._pre_init()
        GladeDelegate.__init__(self,
                               gladefile=self.gladefile,
                               toplevel_name=self.toplevel_name)
        self._post_init()

    def _pre_init(self):
        # FIXME: Perhaps we should add a proper API to add a search to
        #        an application, however it's a bit complicated since the
        #        search creation is done in two steps due to how kiwi auto
        #        signal connection works
        if self.search_table is not None:
            self._create_search()
        self._app_settings = api.user_settings.get('app-ui', {})
        # Create actions, this must be done before the constructor
        # is called, eg when signals are autoconnected
        self.create_actions()

    def _post_init(self):
        self.create_ui()

        if self.search_table is not None:
            self.attach_slave('search_holder', self.search)
            self.create_filters()
            self._restore_filter_settings()
            self.search.focus_search_entry()

    def _create_search(self):
        # This does the first part of the search creation,
        # this need to be done here so that self.results is set when we
        # call GladeDelegate.__init__()

        self.executer = QueryExecuter(self.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.executer.set_table(self.search_table)

        self.search = SearchSlave(self.get_columns(),
                                  restore_name=self.__class__.__name__)
        self.search.enable_advanced_search()
        self.search.set_query_executer(self.executer)
        self.search.connect("search-completed",
                            self._on_search__search_completed)
        self.results = self.search.result_view
        search_filter = self.search.get_primary_filter()
        search_filter.set_label(self.search_label)

    def _display_open_inventory_message(self):
        msg = _(u'There is an inventory process open at the moment.\n'
                'While that inventory is open, you will be unable to do '
                'operations that modify your stock.')
        self.inventory_bar = self.window.add_info_bar(gtk.MESSAGE_WARNING, msg)

    def _save_filter_settings(self):
        if self._loading_filters:
            return
        filter_states = self.search.get_filter_states()
        settings = self._app_settings.setdefault(self.app_name, {})
        settings['filter-states'] = filter_states

    def _restore_filter_settings(self):
        self._loading_filters = True
        settings = self._app_settings.setdefault(self.app_name, {})
        filter_states = settings.get('filter-states')
        if filter_states is not None:
            # Disable auto search to avoid an extra query when restoring the
            # state
            self.search.set_auto_search(False)
            self.search.set_filter_states(filter_states)
            self.search.set_auto_search(True)
        self._loading_filters = False

    #
    # Overridables
    #

    def create_actions(self):
        """This is called before the BaseWindow constructor, so we
        can create actions that can be autoconnected.
        The widgets and actions loaded from builder files are not set
        yet"""

    def create_ui(self):
        """This is called when the UI such as GtkWidgets should be
        created. Glade widgets are now created and can be accessed
        in the instance.
        """

    def activate(self, params):
        """This is when you switch to an application.
        You should setup widget sensitivity here and refresh lists etc
        :params params: an dictionary with optional parameters.
        """

    def setup_focus(self):
        """Define this method on child when it's needed.
        This is for calling grab_focus(), it's called after the window
        is shown. focus chains should be created in create_ui()"""

    def get_title(self):
        # This method must be redefined in child when it's needed
        branch = api.get_current_branch(self.store)
        return _('[%s] - %s') % (branch.get_description(), self.app_title)

    def can_change_application(self):
        """Define if we can change the current application or not.

        :returns: True if we can change the application, False otherwise.
        """
        return True

    def can_close_application(self):
        """Define if we can close the current application or not.

        :returns: True if we can close the application, False otherwise.
        """
        return True

    def set_open_inventory(self):
        """ Subclasses should overide this if they call
        :obj:`.check_open_inventory`.

        This method will be called it there is an open inventory, so the
        application can disable some funcionalities
        """
        raise NotImplementedError

    def new_activate(self):
        """Called when the New toolbar item is activated"""
        raise NotImplementedError

    def search_activate(self):
        """Called when the Search toolbar item is activated"""
        raise NotImplementedError

    def print_activate(self):
        """Called when the Print toolbar item is activated"""
        if self.search_table is None:
            raise NotImplementedError

        if self.results.get_selection_mode() == gtk.SELECTION_MULTIPLE:
            results = self.results.get_selected_rows()
        else:
            result = self.results.get_selected()
            results = [result] if result else None

        # There are no itens selected. We should print the entire list
        if not results:
            results = list(self.search.get_last_results())

        self.print_report(self.report_table, self.results, results)

    def export_spreadsheet_activate(self):
        """Called when the Export menu item is activated"""
        if self.search_table is None:
            raise NotImplementedError

        sse = SpreadSheetExporter()
        sse.export(object_list=self.results,
                   name=self.app_name,
                   filename_prefix=self.app.name)

    def create_filters(self):
        """Implement this to provide filters for the search container"""

    def search_completed(self, results, states):
        """Implement this if you want to know when a search has
        been completed.

        :param results: the search results
        :param states: search states used to construct the search query search
        """

    #
    # Public API
    #

    def add_ui_actions(self,
                       ui_string,
                       actions,
                       name='Actions',
                       action_type='normal',
                       filename=None):
        return self.window.add_ui_actions(ui_string=ui_string,
                                          actions=actions,
                                          name=name,
                                          action_type=action_type,
                                          filename=filename,
                                          instance=self)

    def add_tool_menu_actions(self, actions):
        return self.window.add_tool_menu_actions(actions=actions)

    def set_help_section(self, label, section):
        self.window.set_help_section(label=label, section=section)

    def get_statusbar_message_area(self):
        return self.window.statusbar.message_area

    def print_report(self, report_class, *args, **kwargs):
        filters = self.search.get_search_filters()
        if filters:
            kwargs['filters'] = filters

        print_report(report_class, *args, **kwargs)

    def set_sensitive(self, widgets, value):
        """Set the *widgets* sensitivity based on *value*

        If a sensitive group was registered for any widget,
        it's validation function will be tested and, if ``False``
        is returned, it will be set insensitive, ignoring *value*

        :param widgets: a list of widgets
        :param value: either `True` or `False`
        """
        # FIXME: Maybe this should ne done on kiwi?
        for widget in widgets:
            sensitive = value

            for validator in self._sensitive_group.get(widget, []):
                if not validator[0](*validator[1]):
                    sensitive = False
                    break

            widget.set_sensitive(sensitive)

    def register_sensitive_group(self, widgets, validation_func, *args):
        """Register widgets on a sensitive group.

        Everytime :obj:`.set_sensitive()` is called, if there is any
        validation function for a given widget on sensitive group,
        then that will be used to decide if it gets sensitive or
        insensitive.

        :param widgets: a list of widgets
        :param validation_func: a function for validation. It should
            return either ``True`` or ``False``.
        :param args: args that will be passed to *validation_func*
        """
        assert callable(validation_func)

        for widget in widgets:
            validators = self._sensitive_group.setdefault(widget, set())
            validators.add((validation_func, args))

    def run_dialog(self, dialog_class, *args, **kwargs):
        """ Encapsuled method for running dialogs. """
        return run_dialog(dialog_class, self, *args, **kwargs)

    @cached_function()
    def has_open_inventory(self):
        return Inventory.has_open(self.store,
                                  api.get_current_branch(self.store))

    def check_open_inventory(self):
        """Checks if there is an open inventory.

        In the case there is one, will call set_open_inventory (subclasses
        should implement it).

        Returns True if there is an open inventory. False otherwise
        """
        inventory_bar = getattr(self, 'inventory_bar', None)

        if self.has_open_inventory():
            if inventory_bar:
                inventory_bar.show()
            else:
                self._display_open_inventory_message()
            self.set_open_inventory()
            return True
        elif inventory_bar:
            inventory_bar.hide()
            return False

    # FIXME: Most of these should be removed and access the search API
    #        directly, eg, self.search.clear() etc

    def add_filter(self,
                   search_filter,
                   position=SearchFilterPosition.BOTTOM,
                   columns=None,
                   callback=None):
        """
        See :class:`SearchSlave.add_filter`
        """
        self.search.add_filter(search_filter, position, columns, callback)

    def set_text_field_columns(self, columns):
        """
        See :class:`SearchSlave.set_text_field_columns`
        """
        self.search.set_text_field_columns(columns)

    def refresh(self):
        """
        See :class:`stoqlib.gui.search.searchslave.SearchSlave.refresh`
        """
        self.search.refresh()

    def clear(self):
        """
        See :class:`stoqlib.gui.search.searchslave.SearchSlave.clear`
        """
        self.search.clear()

    def select_result(self, result):
        """Select the object in the result list

        If the object is not in the list (filtered out, for instance), no error
        is thrown and nothing is selected
        """
        try:
            self.results.select(result)
        except ValueError:
            pass

    #
    # Callbacks
    #

    def _on_search__search_completed(self, search, results, states):
        self.search_completed(results, states)

        has_results = len(results)
        for widget in [self.window.Print, self.window.ExportSpreadSheet]:
            widget.set_sensitive(has_results)
        self._save_filter_settings()
Ejemplo n.º 37
0
class PurchaseSelectionStep(BaseWizardStep):
    gladefile = 'PurchaseSelectionStep'

    def __init__(self, wizard, store):
        self._next_step = None
        BaseWizardStep.__init__(self, store, wizard)
        self.setup_slaves()

    def _refresh_next(self, validation_value):
        has_selection = self.search.results.get_selected() is not None
        self.wizard.refresh_next(has_selection)

    def _create_search(self):
        self.search = SearchSlave(self._get_columns(),
                                  restore_name=self.__class__.__name__)
        self.search.enable_advanced_search()
        self.attach_slave('searchbar_holder', self.search)
        self.executer = QueryExecuter(self.store)
        self.search.set_query_executer(self.executer)
        self.executer.set_table(PurchaseOrderView)
        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.results.connect('row-activated',
                                    self._on_results__row_activated)
        self.search.focus_search_entry()

    def _create_filters(self):
        self.search.set_text_field_columns(['supplier_name'])

    def get_extra_query(self, states):
        return PurchaseOrderView.status == PurchaseOrder.ORDER_CONFIRMED

    def _get_columns(self):
        return [
            IdentifierColumn('identifier', sorted=True),
            SearchColumn('open_date',
                         title=_('Date Started'),
                         data_type=datetime.date,
                         width=100),
            SearchColumn('expected_receival_date',
                         data_type=datetime.date,
                         title=_('Expected Receival'),
                         visible=False),
            SearchColumn('supplier_name',
                         title=_('Supplier'),
                         data_type=str,
                         searchable=True,
                         width=130,
                         expand=True),
            SearchColumn('ordered_quantity',
                         title=_('Qty Ordered'),
                         data_type=Decimal,
                         width=110,
                         format_func=format_quantity),
            SearchColumn('received_quantity',
                         title=_('Qty Received'),
                         data_type=Decimal,
                         width=145,
                         format_func=format_quantity),
            SearchColumn('total',
                         title=_('Order Total'),
                         data_type=currency,
                         width=120)
        ]

    def _update_view(self):
        has_selection = self.search.results.get_selected() is not None
        self.details_button.set_sensitive(has_selection)

    #
    # WizardStep hooks
    #

    def post_init(self):
        self._update_view()
        self.register_validate_function(self._refresh_next)
        self.force_validation()

    def next_step(self):
        self.search.save_columns()
        selected = self.search.results.get_selected()
        purchase = selected.purchase

        # We cannot create the model in the wizard since we haven't
        # selected a PurchaseOrder yet which ReceivingOrder depends on
        # Create the order here since this is the first place where we
        # actually have a purchase selected
        if not self.wizard.model:
            self.wizard.model = self.model = ReceivingOrder(
                responsible=api.get_current_user(self.store),
                supplier=purchase.supplier,
                invoice_number=None,
                branch=purchase.branch,
                purchase=purchase,
                store=self.store)

        # Remove all the items added previously, used if we hit back
        # at any point in the wizard.
        if self.model.purchase != purchase:
            self.model.remove_items()
            # This forces ReceivingOrderProductStep to create a new model
            self._next_step = None

        if selected:
            self.model.purchase = purchase
            self.model.branch = purchase.branch
            self.model.supplier = purchase.supplier
            self.model.transporter = purchase.transporter
        else:
            self.model.purchase = None

        # FIXME: Improve the infrastructure to avoid this local caching of
        #        Wizard steps.
        if not self._next_step:
            # Remove all the items added previously, used if we hit back
            # at any point in the wizard.
            self._next_step = ReceivingOrderItemStep(self.store, self.wizard,
                                                     self.model, self)
        return self._next_step

    def has_previous_step(self):
        return False

    def setup_slaves(self):
        self._create_search()

    #
    # Kiwi callbacks
    #


#     def on_searchbar_activate(self, slave, objs):
#         """Use this callback with SearchBar search-activate signal"""
#         self.results.add_list(objs, clear=True)
#         has_selection = self.results.get_selected() is not None
#         self.wizard.refresh_next(has_selection)

    def _on_results__selection_changed(self, results, purchase_order_view):
        self.force_validation()
        self._update_view()

    def _on_results__row_activated(self, results, purchase_order_view):
        run_dialog(PurchaseDetailsDialog,
                   self.wizard,
                   self.store,
                   model=purchase_order_view.purchase)

    def on_details_button__clicked(self, *args):
        selected = self.search.results.get_selected()
        if not selected:
            raise ValueError('You should have one order selected '
                             'at this point, got nothing')
        run_dialog(PurchaseDetailsDialog,
                   self.wizard,
                   self.store,
                   model=selected.purchase)
Ejemplo n.º 38
0
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', 'identifier_str'])

    def _get_columns(self):
        return [
            IdentifierColumn('identifier', title=_('Loan #'), sorted=True),
            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):
        can_continue = False
        selected_rows = self.search.results.get_selected_rows()
        if selected_rows:
            client = selected_rows[0].client_id
            branch = selected_rows[0].branch_id
            # Only loans that belong to the same client and are from the same
            # branch can be closed together
            can_continue = all(v.client_id == client and v.branch_id == branch
                               for v in selected_rows)
        self.wizard.refresh_next(can_continue)

    def get_extra_query(self, states):
        return LoanView.status == Loan.STATUS_OPEN

    def setup_slaves(self):
        self.search = SearchSlave(self._get_columns(),
                                  restore_name=self.__class__.__name__,
                                  store=self.store,
                                  search_spec=LoanView)
        self.search.enable_advanced_search()
        self.attach_slave('place_holder', self.search)
        executer = self.search.get_query_executer()
        executer.add_query_callback(self.get_extra_query)
        self._create_filters()
        self.search.results.connect('selection-changed',
                                    self._on_results_selection_changed)
        self.search.results.set_selection_mode(gtk.SELECTION_MULTIPLE)
        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):
        # FIXME: For some reason, the loan isn't in self.store
        views = self.search.results.get_selected_rows()
        self.wizard.models = [self.store.fetch(v.loan) for v in views]
        return LoanItemSelectionStep(self.wizard, self, self.store,
                                     self.wizard.models)

    #
    # Callbacks
    #

    def _on_results_selection_changed(self, widget, selection):
        self._refresh_next()