def sorts_right(objects): assert {y.expected_position for y in values(objects) } == set(range(len(objects))), "Borken test" sort_after(objects) assert [x.expected_position for x in values(objects) ] == list(range(len(objects))), keys(objects)
def test_unbound_error(): class MyBasket(Basket): orange = Fruit(taste='sour') expected = 'fruits of MyBasket is not bound, look in _declared_members[fruits] for the declared copy of this, or bind first' basket = MyBasket() assert repr(basket.fruits) == expected with pytest.raises(NotBoundYetException) as e: items(basket.fruits) with pytest.raises(NotBoundYetException) as e2: str(basket.fruits) with pytest.raises(NotBoundYetException) as e3: keys(basket.fruits) with pytest.raises(NotBoundYetException) as e4: values(basket.fruits) with pytest.raises(NotBoundYetException) as e5: for _ in basket.fruits: pass # pragma: no cover as it is supposed to raise on iter assert str(e.value) == str(e2.value) == str(e3.value) == str( e4.value) == str(e5.value) assert str(e.value) == expected
def render_fields(self): r = [] for field in values(self.fields): r.append(field.__html__()) # We need to preserve all other GET parameters, so we can e.g. filter in two forms on the same page, and keep sorting after filtering own_field_paths = {f.iommi_path for f in values(self.fields)} for k, v in items(self.get_request().GET): if k not in own_field_paths and not k.startswith('-'): r.append(format_html('<input type="hidden" name="{}" value="{}" />', k, v)) return format_html('{}\n' * len(r), *r)
def _freetext_to_q(self, token): if all(not v.freetext for v in values(self.filters)): raise QueryException('There are no freetext filters available') assert len(token) == 1 token = token[0].strip('"') return reduce(operator.or_, [ Q( **{ filter.attr + '__' + filter.query_operator_to_q_operator(':'): token }) for filter in values(self.filters) if filter.freetext ])
def on_bind(self): bind_members(self, name='sub_menu') if self.sort: self.sub_menu = Struct( {item._name: item for item in sorted(values(self.sub_menu), key=lambda x: x.display_name)} )
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 render_text_or_children(self, context): request = self.get_request() return format_html( '{}' * len(self.children), *[ as_html(part=x, context=context, request=request) for x in values(self.children) ])
def create_or_edit_object__post_handler(*, form, is_create, **_): if is_create: assert form.instance is None form.instance = form.model() for field in values(form.fields): # two phase save for creation in django, have to save main object before related stuff if not field.extra.get('django_related_field', False): form.apply_field(field=field, instance=form.instance) try: form.instance.validate_unique() except ValidationError as e: form.errors.update(set(e.messages)) form._valid = False # pragma: no mutate. False here is faster, but setting it to None is also fine, it just means _valid will be calculated the next time form.is_valid() is called if not form.is_valid(): return if is_create: # two phase save for creation in django... form.instance.save() form.apply(form.instance) if not is_create: try: form.instance.validate_unique() except ValidationError as e: form.errors.update(set(e.messages)) form._valid = False # pragma: no mutate. False here is faster, but setting it to None is also fine, it just means _valid will be calculated the next time form.is_valid() is called if form.is_valid(): form.instance.save() form.extra.on_save(form=form, instance=form.instance) return create_or_edit_object_redirect(is_create, form.extra.redirect_to, form.get_request(), form.extra.redirect, form)
def apply(self, instance): """ Write the new values specified in the form into the instance specified. """ assert self.is_valid() for field in values(self.fields): self.apply_field(instance=instance, field=field) return instance
def get_errors(self): self.is_valid() r = {} if self.errors: r['global'] = self.errors field_errors = {x._name: x.errors for x in values(self.fields) if x.errors} if field_errors: r['fields'] = field_errors return r
def _validate(item): for sub_item in values(item.sub_menu): if sub_item.url is None or '://' in sub_item.url or sub_item.url.startswith('#'): continue _validate(sub_item) path = urlparse(sub_item.url).path paths[path].append(sub_item.iommi_path)
def group_actions(actions: Dict[str, Action]): actions_with_group = (action for action in values(actions) if action.group is not None) grouped_actions: List[Tuple[str, str, List[Action]]] = [ (group_name, slugify(group_name), list(actions_in_group)) for group_name, actions_in_group in groupby(actions_with_group, key=lambda l: l.group) ] for _, _, actions_in_group in grouped_actions: for action in actions_in_group: action.attrs.role = 'menuitem' action.attrs['class']['dropdown-item'] = True actions_without_group = [ action for action in values(actions) if action.group is None ] return actions_without_group, grouped_actions
def is_valid(self): if self._valid is None: self.validate() for field in values(self.fields): if field.errors: self._valid = False break else: self._valid = not self.errors return self._valid
def get_query_string(self): """ Based on the data in the request, return the equivalent query string that you can use with parse_query_string() to create a query set. """ form = self.form request = self.get_request() if request is None: return '' if request_data(request).get(self.get_advanced_query_param(), '').strip(): return request_data(request).get(self.get_advanced_query_param()) elif form.is_valid(): def expr(field, is_list, value): if is_list: return '(' + ' OR '.join([expr(field, is_list=False, value=x) for x in field.value]) + ')' return build_query_expression(filter=self.filters[field._name], value=value) result = [ expr(field, field.is_list, field.value) for field in values(form.fields) if field._name != FREETEXT_SEARCH_NAME and field._name in self.filters and field.value not in (None, '', []) ] if FREETEXT_SEARCH_NAME in form.fields: freetext = form.fields[FREETEXT_SEARCH_NAME].value if freetext: result.append( '(%s)' % ' or '.join( [ f'{filter.query_name}:{to_string_surrounded_by_quote(freetext)}' for filter in values(self.filters) if filter.freetext ] ) ) return ' and '.join(result) else: return ''
def test_bulk_edit_for_non_unique(settings): settings.ROOT_URLCONF = __name__ request = staff_req('get') p = Admin.list( request=request, app_name='tests', model_name='adminunique', parts__list_tests_adminunique__columns__foo__bulk__include=True, ) p = p.bind(request=request) assert [x._name for x in values(p.parts.list_tests_adminunique.columns) if x.bulk.include] == ['foo']
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 _set_active(item): nonlocal current_parts_matching nonlocal current for sub_item in values(item.sub_menu): _set_active(sub_item) if sub_item.url is None or '://' in sub_item.url: continue parsed_url = urlparse(sub_item.url).path if current_path.startswith(parsed_url): parts = PurePosixPath(unquote(parsed_url)).parts matching_parts = 0 for item in range(min(len(parts), len(path_parts))): if parts[item] is path_parts[item]: matching_parts += 1 if matching_parts > current_parts_matching: current = sub_item current_parts_matching = matching_parts
def is_target(self): return any(action.is_target() for action in values(self.actions))
Header, html, Menu, MenuItem, Page, Table, ) from iommi.base import ( items, values, ) from iommi.reinvokable import reinvokable app_verbose_name_by_label = { config.label: config.verbose_name for config in values(django_apps.app_configs) } joined_app_name_and_model = { f'{app_name}_{model_name}' for app_name, models in items(django_apps.all_models) for model_name, model in items(models) } def require_login(view): @wraps(view) def wrapper(cls, request, *args, **kwargs): if not getattr(request, 'user', None) or not request.user.is_authenticated: return HttpResponseRedirect(f'{reverse(Auth.login)}?{urlencode(dict(next=request.path))}')
def __repr__(self): r = f'{self._name}' if self.sub_menu: for items in values(self.sub_menu): r += ''.join([f'\n {x}' for x in repr(items).split('\n')]) return r
def render_text_or_children(self, context): assert not isinstance(context, RequestContext) return format_html( '{}' * len(self.children), *[as_html(part=x, context=context) for x in values(self.children)])
def validate(self): for field in values(self.fields): field.post_validation(**field.iommi_evaluate_parameters()) self.post_validation(**self.iommi_evaluate_parameters()) return self
class Page(Part): """ A page is used to compose iommi parts into a bigger whole. See the `howto <https://docs.iommi.rocks/en/latest/howto.html#parts-pages>`_ for example usages. """ title: str = EvaluatedRefinable() member_class: Type[Fragment] = Refinable() context = Refinable( ) # context is evaluated, but in a special way so gets no EvaluatedRefinable type class Meta: member_class = Fragment @reinvokable @dispatch( parts=EMPTY, context=EMPTY, ) def __init__(self, *, _parts_dict: Dict[str, PartType] = None, parts: dict, **kwargs): super(Page, self).__init__(**kwargs) self.parts = { } # This is just so that the repr can survive if it gets triggered before parts is set properly # First we have to up sample parts that aren't Part into Fragment def as_fragment_if_needed(k, v): if v is None: return None if not isinstance(v, (dict, Traversable)): return Fragment(children__text=v, _name=k) else: return v _parts_dict = { k: as_fragment_if_needed(k, v) for k, v in items(_parts_dict) } parts = Namespace( {k: as_fragment_if_needed(k, v) for k, v in items(parts)}) collect_members(self, name='parts', items=parts, items_dict=_parts_dict, cls=self.get_meta().member_class) def on_bind(self) -> None: bind_members(self, name='parts') if self.context and self.iommi_parent() != None: assert False, 'The context property is only valid on the root page' def own_evaluate_parameters(self): return dict(page=self) @dispatch(render=lambda rendered: format_html('{}' * len(rendered), *values(rendered))) def __html__(self, *, render=None): self.context = evaluate_strict_container( self.context or {}, **self.iommi_evaluate_parameters()) rendered = { name: as_html(request=self.get_request(), part=part, context=self.iommi_evaluate_parameters()) for name, part in items(self.parts) } return render(rendered) def as_view(self): return build_as_view_wrapper(self)
def test_bulk_edit_for_non_unique(): request = req('get') request.user = Struct(is_staff=True) p = Admin.list(request=request, app_name='tests', model_name='adminunique') p = p.bind(request=request) assert [x._name for x in values(p.parts.list_tests_adminunique.columns) if x.bulk.include] == ['foo']