""" open/dulcinea/lib/ui/form/search_widget.qpy """ from qp.fill.widget import CompositeWidget, StringWidget from qp.fill.widget import SubmitWidget, SingleSelectWidget from qp.pub.common import get_fields from dulcinea.ui.form.select_widget import BigMultipleSelectWidget from dulcinea.user import list_users def get_user_search_domain(): for user in list_users(): yield (user, ' '.join((user.get_id(), user.format_realname() or '', user.get_email() or ''))) class SearchWidget(CompositeWidget): """ This widget provides a simple mechanism for choosing objects based on word matching. """ def __init__(self, name, value, get_domain=get_user_search_domain, **keywords): """ get_domain(), when called, must return or generate a sequence of (obj, text) tuples. The value of the widget is the list of all of the objs for which the corresponding text contains a word in the value of the keywords subwidget. """ CompositeWidget.__init__(self, name, value, **keywords) self.add(StringWidget, "keywords", size=30) self.add(SubmitWidget, "submit_search", value="Search") self.get_domain = get_domain def _parse(self, request): CompositeWidget._parse(self, request) domain = [domain_pair for domain_pair in self.get_domain()] self.value = [obj for obj, text in domain] # if self.get('submit_search'): keywords = self.get("keywords") if keywords: keyword_list = [keyword.lower() for keyword in keywords.split()] matches = [] for obj, text in domain: text = text.lower() for keyword in keyword_list: if keyword in text: matches.append(obj) break if matches: self.value = matches else: self.set_error("No matches found for '%s'." % keywords) self.get_widget("keywords").set_value(None) def get_submit(self): return self.get("submit_search") class SearchSelectOneWidget(CompositeWidget): """ This widget provides a simple mechanism for choosing objects based on word matching followed by direct selection by the user. """ def __init__(self, name, value=None, get_domain=get_user_search_domain, title=None, required=False, sort=True, **keywords): """ get_domain(), when called, must return or generate a sequence of (obj, text) tuples. The value of the widget is the list of all of the objs for which the corresponding text contains a word in the value of the keywords subwidget. """ CompositeWidget.__init__(self, name, title=title, required=required) if value: options = [value, None] else: options = [value] self.add(SearchWidget, "search", value=None, get_domain=get_domain, **keywords) self.add(SingleSelectWidget, "select", value=value, options=options, verify_selection=True, sort=True) search_widget = self.get_widget("search") select_widget = self.get_widget("select") keywords = search_widget.get('keywords') if search_widget.get_submit(): options = self.get("search") self.parse() self.set_error(None) select_widget.set_error(None) if keywords: select_widget.set_hint( "Choose from search results for '%s'" % keywords) else: if get_fields(): options = [obj for obj, text in get_domain()] else: options = [None] if value not in options: options.append(value) if None not in options: options.append(None) select_widget.set_options(options, sort=sort) if (search_widget.get_submit() and self.get("select") is None and len(options) == 2): for option in options: if option is not None: select_widget.set_value(option) select_widget.set_error(None) select_widget.set_hint( "There was one match for '%s'" % keywords) def _parse(self, request): CompositeWidget._parse(self, request) self.value = self.get("select") def get_submit(self): return self.get_widget("search").get_submit() def render_content:xml(self): self.get_widget('search').render() if self.get('select') or self.get('search'): self.get_widget('select').render() class SearchSelectWidget(CompositeWidget): """ This widget provides a simple mechanism for choosing objects based on word matching followed by direct selection by the user. """ def __init__(self, name, value=None, get_domain=get_user_search_domain, size=6, **keywords): """ get_domain(), when called, must return or generate a sequence of (obj, text) tuples. The value of the widget is the list of all of the objs for which the corresponding text contains a word in the value of the keywords subwidget. if add_select_button is True, a submit widget is added just below the search widget that when pressed has the effect of setting the value of the search widget so that forms can re-rendered based on the value of the search widget. """ CompositeWidget.__init__(self, name, value) self.add(SearchWidget, "search", value=None, get_domain=get_domain, **keywords) search_results = self.get("search") or [] options = list(set(search_results).union(set(value or []))) if options: self.add(BigMultipleSelectWidget, "select", value=value, options=options, title="Matches", size=size) self.add(SubmitWidget, "submit_selection", value="Select") self._force_select_to_show_current_selected(get_domain) def _force_select_to_show_current_selected(self, get_domain): """Force the selected sub-widget of the BigMultipleSelect to always represent the current selection in the face of possibly shrinking options """ all_options = SingleSelectWidget( 'all_options', value=None, options=list(get_domain())).options select_widget = self.get_widget('select') selected_widget = select_widget.get_widget('selected') keys = set((select_widget.get('selected_keys') or '').split(',')) if len(keys) > len(selected_widget.options): selected_widget.options = list(set(selected_widget.options).union( set([(option[0], option[0], option[2]) for option in all_options if option[2] in keys]))) def description_key(option): return str(option[0]).lower() selected_widget.options.sort(key=description_key) def _parse(self, request): CompositeWidget._parse(self, request) self.value = self.get("select") def format_search_select_css(): return """ div.SearchSelectOneWidget br { display: none; } div.SearchSelectOneWidget div.widget { float: left; } div.SearchSelectOneWidget div.hint { clear: left; } """