def on_bind(self) -> None: bind_members(self, name='filters') self.advanced_simple_toggle = self.advanced_simple_toggle.bind(parent=self) request = self.get_request() self.query_advanced_value = request_data(request).get(self.get_advanced_query_param(), '') if request else '' # TODO: should it be possible to have freetext as a callable? this code just treats callables as truthy if any(f.freetext for f in values(declared_members(self)['filters'])): declared_members(self.form).fields[FREETEXT_SEARCH_NAME].include = True declared_fields = declared_members(self.form)['fields'] for name, filter in items(self.filters): assert filter.attr or not getattr(filter.value_to_q, 'iommi_needs_attr', False), f"{name} cannot be a part of a query, it has no attr or value_to_q so we don't know what to search for" if name in declared_fields: field = setdefaults_path( Namespace(), _name=name, attr=name if filter.attr is MISSING else filter.attr, model_field=filter.model_field, ) declared_fields[name] = declared_fields[name].reinvoke(field) set_declared_member(self.form, 'fields', declared_fields) for name, field in items(declared_fields): if name == FREETEXT_SEARCH_NAME: continue if name not in self.filters: field.include = False bind_members(self, name='endpoints') self.form = self.form.bind(parent=self) self._bound_members.form = self.form
def test_collect_sets_name(): class MyBasket(Basket): orange = Fruit(taste='sour') basket = MyBasket() assert declared_members(basket).fruits.orange._name == 'orange' basket = MyBasket(fruits__orange=Fruit(taste='sour')) assert declared_members(basket).fruits.orange._name == 'orange'
def on_bind(self) -> None: bind_members(self, name='filters') request = self.get_request() self.query_advanced_value = request_data(request).get( self.get_advanced_query_param(), '') if request else '' # TODO: should it be possible to have freetext as a callable? this code just treats callables as truthy if any(f.freetext for f in values(declared_members(self)['filters'])): set_and_remember_for_reinvoke(declared_members( self.form).fields[FREETEXT_SEARCH_NAME], include=True) declared_fields = declared_members(self.form)['fields'] for name, filter in items(self.filters): is_valid, message = filter.is_valid_filter(name=name, filter=filter) assert is_valid, message if name in declared_fields: field = setdefaults_path( Namespace(), _name=name, attr=name if filter.attr is MISSING else filter.attr, model_field=filter.model_field, help__include=False, ) declared_fields[name] = reinvoke(declared_fields[name], field) set_declared_member(self.form, 'fields', declared_fields) # Remove fields from the form that correspond to non-included filters declared_filters = declared_members(self)['filters'] for name, field in items(declared_fields): if name == FREETEXT_SEARCH_NAME: continue # We need to check if it's in declared_filters first, otherwise we remove any injected fields if name in declared_filters and name not in self.filters: set_and_remember_for_reinvoke(field, include=False) bind_members(self, name='endpoints') self.form = self.form.bind(parent=self) self._bound_members.form = self.form self.advanced = self._bound_members.advanced = self.advanced.bind( parent=self) self.form_container = self.form_container.bind(parent=self) self.filter_name_by_query_name = { x.query_name: name for name, x in items(self.filters) }
def rows(node, name='', path=None): if path is None: path = [] is_struct = type(node) is Struct is_bound = getattr(node, '_is_bound', False) try: p = node.iommi_path if is_bound else None except PathNotFoundException: p = None type_name = type(node).__name__ if not is_struct else None base_type_name = type_name if isinstance(node, Members) and node._declared_members: if name == 'parts': member_type = 'Part' else: member_type = type(list(values(declared_members(node)))[0]).__name__ type_name = f'Members[{member_type}]' children = [] if isinstance(node, dict): children = list(node.items()) elif isinstance(node, Traversable): children = [ ( k, node.iommi_bound_members().get(k, v) ) for k, v in items(declared_members(node)) ] if (isinstance(node, Members) or isinstance(node, dict)) and not children: return yield Struct( name=name, obj=node, type=type_name, base_type=base_type_name, path=p, dunder_path='__'.join(path), included=is_bound ) for k, v in children: yield from rows(v, name=k, path=path + [k])
def test_from_model_foreign_key(): class MyQuery(Query): class Meta: filters = Query.filters_from_model(model=Bar) t = MyQuery().bind(request=req('get')) assert list(declared_members(t).filters.keys()) == ['id', 'foo'] assert isinstance(t.filters['foo'].choices, QuerySet)
def test_page_constructor(): class MyPage(Page): h1 = html.h1() my_page = MyPage(parts__foo=html.div(_name='foo'), parts__bar=html.div()) assert ['h1', 'foo', 'bar'] == list(declared_members(my_page).parts.keys()) my_page = my_page.bind(request=None) assert ['h1', 'foo', 'bar'] == list(my_page.parts.keys())
def bind_members(parent: Traversable, *, name: str, cls=Members, unknown_types_fall_through=False) -> None: m = cls( _name=name, _declared_members=declared_members(parent)[name], unknown_types_fall_through=unknown_types_fall_through, ) m = m.bind(parent=parent) setattr(parent._bound_members, name, m) setattr(parent, name, m._bound_members)
def __init__(self, **kwargs): super(Advanced, self).__init__(**kwargs) toggle = setdefaults_path( Namespace(), self.toggle, _name='toggle', call_target=Action, attrs__href='#', attrs__class__iommi_query_toggle_simple_mode=True, attrs={'data-advanced-mode': 'simple'}, display_name=gettext('Switch to advanced search'), ) self.toggle = declared_members(self).toggle = toggle()
def bind_members(parent: Traversable, *, name: str, cls=Members, unknown_types_fall_through=False, lazy=True) -> None: """ This is the companion function to `collect_members`. It is used at bind time to recursively (and by default lazily(!)) bind the parts of a container. """ m = cls( _name=name, _declared_members=declared_members(parent)[name], unknown_types_fall_through=unknown_types_fall_through, ) assert parent._is_bound m = m.bind(parent=parent) setattr(parent._bound_members, name, m) setattr(parent, name, m._bound_members) if not lazy: _force_bind_all(m._bound_members)
def test_promote_str_to_fragment_for_page(): class MyPage(Page): foo = 'asd' page = MyPage() assert isinstance(declared_members(page).parts.foo, Fragment)
def __init__(self, *, model=None, rows=None, filters=None, _filters_dict=None, auto, **kwargs): assert isinstance(filters, dict) if auto: auto = QueryAutoConfig(**auto) auto_model, auto_rows, filters = self._from_model( model=auto.model, rows=auto.rows, filters=filters, include=auto.include, exclude=auto.exclude, ) assert model is None, "You can't use the auto feature and explicitly pass model. " \ "Either pass auto__model, or we will set the model for you from auto__rows" model = auto_model if rows is None: rows = auto_rows model, rows = model_and_rows(model, rows) setdefaults_path( kwargs, form__call_target=self.get_meta().form_class, ) self._form = None self.query_advanced_value = None self.query_error = None super(Query, self).__init__(model=model, rows=rows, **kwargs) collect_members(self, name='filters', items=filters, items_dict=_filters_dict, cls=self.get_meta().member_class) field_class = self.get_meta().form_class.get_meta().member_class declared_fields = Struct() declared_fields[FREETEXT_SEARCH_NAME] = field_class( _name=FREETEXT_SEARCH_NAME, display_name='Search', required=False, include=False, ) for name, filter in items(declared_members(self).filters): if filter.attr is None and getattr(filter.value_to_q, 'iommi_needs_attr', False): continue field = setdefaults_path( Namespace(), filter.field, _name=name, model_field=filter.model_field, attr=name if filter.attr is MISSING else filter.attr, call_target__cls=field_class, ) declared_fields[name] = field() # noinspection PyCallingNonCallable self.form: Form = self.form( _name='form', _fields_dict=declared_fields, attrs__method='get', actions__submit__attrs__value='Filter', ) declared_members(self).form = self.form self.advanced_simple_toggle = Action( attrs__href='#', attrs__class__iommi_query_toggle_simple_mode=True, attrs={'data-advanced-mode': 'simple'}, display_name='Switch to advanced search', ) self.form_container = self.form_container() # Filters need to be at the end to not steal the short names set_declared_member(self, 'filters', declared_members(self).pop('filters'))
def test_collect_from_declarative(): class MyBasket(Basket): orange = Fruit(taste='sour') basket = MyBasket() assert declared_members(basket).fruits.orange.taste == 'sour'
def test_collect_from_arg(): basket = Basket(fruits__banana__taste="sweet") assert declared_members(basket).fruits.banana.taste == 'sweet'
def test_empty_collect(): assert declared_members(Basket()).fruits == {}
def test_none_members_should_be_discarded_after_being_allowed_through(): class MyBasket(Basket): orange = Fruit(taste='sour') basket = MyBasket(fruits__orange=None) assert 'orange' not in declared_members(basket).fruits
def __init__(self, *, model=None, rows=None, filters=None, _filters_dict=None, auto=None, **kwargs): assert isinstance(filters, dict) if auto: auto = QueryAutoConfig(**auto) auto_model, auto_rows, filters = self._from_model( model=auto.model, rows=auto.rows, filters=filters, include=auto.include, exclude=auto.exclude, ) assert model is None, "You can't use the auto feature and explicitly pass model. " \ "Either pass auto__model, or we will set the model for you from auto__rows" model = auto_model if rows is None: rows = auto_rows model, rows = model_and_rows(model, rows) setdefaults_path( kwargs, form__call_target=self.get_meta().form_class, ) self.query_advanced_value = None self.query_error = None # Here we need to remove the freetext config from kwargs because we want to # handle it differently from the other fields. # BUT It's not safe to modify kwargs deeply! Because reinvoke() is evil: # It will call init again with the same kwargs + whatever additional kwargs # might have come from a parent or styling info. freetext_config = kwargs.get('form', {}).get('fields', {}).get('freetext', {}) if 'form' in kwargs and 'fields' in kwargs[ 'form'] and 'freetext' in kwargs['form']['fields']: # copy (just) the namespace so we can safely remove freetext from it kwargs = Namespace(flatten(Namespace(kwargs))) freetext_config = kwargs.get('form', {}).get('fields', {}).pop('freetext', {}) super(Query, self).__init__(model=model, rows=rows, **kwargs) collect_members(self, name='filters', items=filters, items_dict=_filters_dict, cls=self.get_meta().member_class) field_class = self.get_meta().form_class.get_meta().member_class declared_fields = Struct() declared_fields[FREETEXT_SEARCH_NAME] = field_class( _name=FREETEXT_SEARCH_NAME, display_name=gettext('Search'), required=False, include=False, help__include=False, **freetext_config) for name, filter in items(declared_members(self).filters): field = setdefaults_path( Namespace(), filter.field, _name=name, model_field=filter.model_field, attr=name if filter.attr is MISSING else filter.attr, call_target__cls=field_class, help__include=False, ) declared_fields[name] = field() # noinspection PyCallingNonCallable self.form: Form = self.form( _name='form', _fields_dict=declared_fields, attrs__method='get', actions__submit=dict( attrs={'data-iommi-filter-button': ''}, display_name=gettext('Filter'), ), ) declared_members(self).form = self.form self.advanced = declared_members(self).advanced = self.advanced( _name='advanced') self.form_container = self.form_container(_name='form_container') # Filters need to be at the end to not steal the short names set_declared_member(self, 'filters', declared_members(self).pop('filters'))
def test_from_model_with_queryset(): t = Query(auto__rows=Foo.objects.all()).bind(request=None) assert list(declared_members(t).filters.keys()) == ['id', 'foo'] assert list(t.filters.keys()) == ['foo']
def test_from_model_with_model_class(): t = Query(auto__model=Foo).bind(request=None) assert list(declared_members(t).filters.keys()) == ['id', 'foo'] assert list(t.filters.keys()) == ['foo']