Beispiel #1
0
    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)
        }
Beispiel #2
0
def reinvoke_new_defaults(obj: Any, additional_kwargs: Dict[str, Any]) -> Any:
    assert is_reinvokable(obj), f'reinvoke_new_defaults() called on object with ' \
                                f'missing @reinvokable constructor decorator: {obj!r}'
    additional_kwargs_namespace = Namespace(additional_kwargs)

    kwargs = Namespace(additional_kwargs_namespace)
    for name, saved_param in items(obj._iommi_saved_params):
        try:
            new_param = getattr_path(additional_kwargs_namespace, name)
        except AttributeError:
            kwargs[name] = saved_param
        else:
            if is_reinvokable(saved_param):
                assert isinstance(new_param, dict)
                kwargs[name] = reinvoke_new_defaults(saved_param, new_param)
            else:
                if isinstance(saved_param, Namespace):
                    kwargs[name] = Namespace(new_param, saved_param)
                else:
                    kwargs[name] = saved_param

    try:
        call_target = kwargs.pop('call_target', None)
        if call_target is not None:
            kwargs['call_target'] = Namespace(
                call_target,
                cls=type(obj)
            )
        else:
            kwargs['call_target'] = type(obj)

        result = kwargs()
    except TypeError as e:
        raise InvalidStyleConfigurationException(
            f'Object {obj!r} could not be updated with style configuration {flatten(additional_kwargs_namespace)}'
        ) from e

    retain_special_cases(obj, result)
    return result
Beispiel #3
0
def test_error_when_trying_to_style_non_existent_attribute():
    class Foo:
        def __repr__(self):
            return '<Foo>'

    style = Namespace(something_that_does_not_exist='!!!')

    with pytest.raises(InvalidStyleConfigurationException) as e:
        apply_style_recursively(style_data=style, obj=Foo())

    assert str(
        e.value
    ) == 'Object <Foo> has no attribute something_that_does_not_exist which the style tried to set.'
Beispiel #4
0
def test_dispatch_none_semantics_after_meta():
    @with_meta
    class MyForm:
        class Meta:
            actions__magic__display_name = "A magic button"

        @dispatch(actions=None)
        def __init__(self, **kwargs):
            self.kwargs = kwargs

    form = MyForm()
    assert form.kwargs == Namespace(
        actions__magic__display_name="A magic button")
Beispiel #5
0
    def component(self, obj, is_root=False):
        """
        Calculate the namespace of additional argument that should be applied
        to the given object. If is_root is set to True, assets might also be
        added to the namespace.
        """
        result = Namespace()

        # TODO: is this wrong? Should it take classes first, then loop through shortcuts?
        for class_name in class_names_for(type(obj)):
            if class_name in self.config:
                config = Namespace(self.config.get(class_name, {}))
                shortcuts_config = Namespace(config.pop('shortcuts', {}))
                result.update(config)

                for shortcut_name in reversed(getattr(obj, '__tri_declarative_shortcut_stack', [])):
                    result = Namespace(result, shortcuts_config.get(shortcut_name, {}))

        if is_root:
            result = Namespace(result, self.root)

        return result
Beispiel #6
0
    def all_models(cls, request, table, call_target=None, **kwargs):
        if not cls.has_permission(request, operation='all_models'):
            raise Http404()

        def rows(admin, **_):

            for app_name, models in items(django_apps.all_models):
                has_yielded_header = False

                for model_name, model in items(models):
                    if not admin.apps.get(f'{app_name}_{model_name}', {}).get(
                            'include', False):
                        continue

                    if not has_yielded_header:
                        yield Struct(
                            name=app_verbose_name_by_label[app_name],
                            verbose_app_name=app_verbose_name_by_label[
                                app_name],
                            url=None,
                            format=lambda row, table, **_: Header(
                                row.name, _name='invalid_name').bind(
                                    parent=table).__html__())
                        has_yielded_header = True

                    yield Struct(
                        verbose_app_name=app_verbose_name_by_label[app_name],
                        app_name=app_name,
                        name=model._meta.verbose_name_plural.capitalize(),
                        url='%s/%s/' % (app_name, model_name),
                        format=lambda row, **_: row.name,
                    )

        table = setdefaults_path(
            Namespace(),
            table,
            title=gettext('All models'),
            call_target__cls=cls.get_meta().table_class,
            sortable=False,
            rows=rows,
            header__template=None,
            page_size=None,
            columns__name=dict(
                cell__url=lambda row, **_: row.url,
                display_name='',
                cell__format=lambda row, **kwargs: row.format(row=row,
                                                              **kwargs),
            ),
        )

        return call_target(parts__all_models=table, **kwargs)
Beispiel #7
0
    def __init__(self, **kwargs):
        """
        Note that, in addition to the parameters with the defined behavior below, you can pass in any keyword argument you need yourself, including callables that conform to the protocol, and they will be added and evaluated as members.

        All these parameters can be callables, and if they are, will be evaluated with the keyword arguments form and field. The only exceptions are `is_valid` (which gets `form`, `field` and `parsed_data`), `render_value` (which takes `form`, `field` and `value`) and `parse` (which gets `form`, `field`, `string_value`). Example of using a lambda to specify a value:

        .. code:: python

            Field(attrs__id=lambda form, field: 'my_id_%s' % field._name)

        :param after: Set the order of columns, see the `howto <https://docs.iommi.rocks/en/latest/howto.html#how-do-i-change-the-order-of-the-fields>`_ for an example.
        :param is_valid: validation function. Should return a tuple of `(bool, reason_for_failure_if_bool_is_false)` or raise ValidationError. Default: `lambda form, field, parsed_data: (True, '')`
        :param parse: parse function. Default just returns the string input unchanged: `lambda form, field, string_value: string_value`
        :param initial: initial value of the field
        :param attr: the attribute path to apply or get the data from. For example using `foo__bar__baz` will result in `your_instance.foo.bar.baz` will be set by the `apply()` function. Defaults to same as name
        :param attrs: a dict containing any custom html attributes to be sent to the `input__template`.
        :param display_name: the text in the HTML label tag. Default: `capitalize(name).replace('_', ' ')`
        :param template: django template filename for the entire row. Normally you shouldn't need to override on this level. Prefer overriding `input__template`, `label__template` or `error__template` as needed.
        :param template_string: You can inline a template string here if it's more convenient than creating a file. Default: `None`
        :param input__template: django template filename for the template for just the input control.
        :param label__template: django template filename for the template for just the label tab.
        :param errors__template: django template filename for the template for just the errors output. Default: `'iommi/form/errors.html'`
        :param required: if the field is a required field. Default: `True`
        :param help_text: The help text will be grabbed from the django model if specified and available.

        :param editable: Default: `True`
        :param strip_input: runs the input data through standard python .strip() before passing it to the parse function (can NOT be callable). Default: `True`
        :param render_value: render the parsed and validated value into a string. Default just converts to unicode: `lambda form, field, value: unicode(value)`
        :param is_list: interpret request data as a list (can NOT be a callable). Default: `False``
        :param read_from_instance: callback to retrieve value from edited instance. Invoked with parameters field and instance.
        :param write_to_instance: callback to write value to instance. Invoked with parameters field, instance and value.
        """

        model_field = kwargs.get('model_field')
        if model_field and model_field.remote_field:
            kwargs['model'] = model_field.remote_field.model

        super(Field, self).__init__(**kwargs)

        # value/value_data_list is the final step that contains parsed and valid data
        self.value = None

        self._choice_tuples = None

        self.non_editable_input = Namespace({
            **flatten(self.input),
            **self.non_editable_input,
            '_name': 'non_editable_input',
        })()
        self.input = self.input(_name='input')
        self.label = self.label(_name='label')
Beispiel #8
0
def reinvoke(obj: Any, additional_kwargs: Dict[str, Any]) -> Any:
    assert is_reinvokable(
        obj
    ), f'reinvoke() called on object with missing @reinvokable constructor decorator: {obj!r}'
    additional_kwargs_namespace = Namespace(additional_kwargs)
    kwargs = {}
    for name, saved_param in items(obj._iommi_saved_params):
        try:
            new_param = getattr_path(additional_kwargs_namespace, name)
        except AttributeError:
            kwargs[name] = saved_param
        else:
            if is_reinvokable(saved_param):
                assert isinstance(new_param, dict)
                kwargs[name] = reinvoke(saved_param, new_param)
            else:
                if isinstance(saved_param, Namespace):
                    kwargs[name] = Namespace(saved_param, new_param)
                else:
                    kwargs[name] = new_param

    kwargs = Namespace(
        additional_kwargs_namespace,
        kwargs)  # Also include those keys not already in the original

    call_target = kwargs.pop('call_target', None)
    if call_target is not None:
        kwargs['call_target'] = Namespace(
            call_target,
            cls=type(obj),
        )
    else:
        kwargs['call_target'] = type(obj)

    result = kwargs()

    retain_special_cases(obj, result)
    return result
Beispiel #9
0
    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()
Beispiel #10
0
def test_namespaciness_override():
    @with_meta()
    class Foo:
        class Meta:
            foo = {'bar': 17}

        @dispatch
        def __init__(self, **kwargs):
            self.kwargs = kwargs

    assert Foo(foo__baz=42).kwargs == Namespace(
        foo__bar=17,
        foo__baz=42,
    )
Beispiel #11
0
def test_namespaciness():
    @with_meta(add_init_kwargs=False)
    class Foo:
        class Meta:
            foo = {'bar': 17}

    class Bar(Foo):
        class Meta:
            foo = {'baz': 42}

    assert Bar().get_meta() == Namespace(
        foo__bar=17,
        foo__baz=42,
    )
Beispiel #12
0
    def reinvoke(self, additional_kwargs: Dict[str, Any]) -> "Traversable":
        assert hasattr(
            self, '_iommi_saved_params'
        ), f'reinvoke() called on class with missing @reinvokable decorator: {self.__class__.__name__}'
        additional_kwargs_namespace = Namespace(additional_kwargs)
        kwargs = {}
        for name, saved_param in items(self._iommi_saved_params):
            try:
                new_param = getattr_path(additional_kwargs_namespace, name)
            except AttributeError:
                kwargs[name] = saved_param
            else:
                if hasattr(saved_param, 'reinvoke'):
                    assert isinstance(new_param, dict)
                    kwargs[name] = saved_param.reinvoke(new_param)
                else:
                    if isinstance(saved_param, Namespace):
                        kwargs[name] = Namespace(saved_param, new_param)
                    else:
                        kwargs[name] = new_param

        additional_kwargs_namespace.pop('call_target', None)

        kwargs = Namespace(
            additional_kwargs_namespace,
            kwargs)  # Also include those keys not already in the original

        result = type(self)(**kwargs)

        result._name = self._name
        __tri_declarative_shortcut_stack = getattr(
            self, '__tri_declarative_shortcut_stack', None)
        if __tri_declarative_shortcut_stack is not None:
            setattr(result, '__tri_declarative_shortcut_stack',
                    __tri_declarative_shortcut_stack)

        return result
Beispiel #13
0
def apply_style_recursively(*, style_data, obj):
    from iommi.member import NotBoundYet, MemberBinder, Members
    if isinstance(obj, NotBoundYet):
        return style_data

    rest_style = Namespace()
    if isinstance(obj, dict):
        result = Namespace(style_data, obj)
        obj.clear()
        obj.update(**result)
    else:
        for k, v in items(style_data):
            if isinstance(v, dict):
                if isinstance(obj, Members):
                    try:
                        child = obj._bound_members[k]
                    except KeyError:
                        child = getattr(obj, k)
                else:
                    child = getattr(obj, k)

                if isinstance(child, MemberBinder):
                    child = obj._bound_members[k]
                if child is not None:
                    rest = apply_style_recursively(style_data=v, obj=child)
                    if rest:
                        rest_style[k] = rest
            else:
                attrib = getattr(obj, k, _no_attribute_sentinel)
                if attrib is _no_attribute_sentinel:
                    raise InvalidStyleConfigurationException(
                        f'Object {obj!r} has no attribute {k} which the style tried to set.'
                    )
                if attrib is None:
                    setattr(obj, k, v)
    return rest_style
Beispiel #14
0
def test_evaluate_attrs_hide_debug_paths():
    actual = evaluate_attrs(
        Struct(
            attrs=Namespace(class__table=True, ),
            _name='foo',
            iommi_dunder_path='<path here>',
        ), )

    expected = {
        'class': {
            'table': True,
        },
        'style': {},
    }

    assert actual == expected
Beispiel #15
0
def test_evaluate_attrs_show_debug_paths():
    actual = evaluate_attrs(
        Struct(
            attrs=Namespace(class__table=True, ),
            name='foo',
            dunder_path=lambda: '<path here>',
        ), )

    expected = {
        'class': {
            'table': True,
        },
        'data-iommi-path': '<path here>',
    }

    assert actual == expected
Beispiel #16
0
def test_error_when_trying_to_style_non_existent_attribute():
    class Foo:
        @reinvokable
        def __init__(self):
            pass

        def __repr__(self):
            return '<Foo>'

    style = Namespace(something_that_does_not_exist='!!!')

    with pytest.raises(InvalidStyleConfigurationException) as e:
        apply_style_data(style_data=style, obj=Foo())

    assert str(
        e.value
    ) == "Object <Foo> could not be updated with style configuration {'something_that_does_not_exist': '!!!'}"
Beispiel #17
0
    def crud(cls,
             request,
             operation,
             form,
             app_name,
             model_name,
             pk=None,
             call_target=None,
             **kwargs):
        model = django_apps.all_models[app_name][model_name]
        instance = model.objects.get(pk=pk) if pk is not None else None

        if not cls.has_permission(
                request, operation=operation, model=model, instance=instance):
            raise Http404()

        def on_save(form, instance, **_):
            message = f'{form.model._meta.verbose_name.capitalize()} {instance} was ' + (
                'created' if form.extra.is_create else 'updated')
            messages.add_message(request,
                                 messages.INFO,
                                 message,
                                 fail_silently=True)

        def on_delete(form, instance, **_):
            message = f'{form.model._meta.verbose_name.capitalize()} {instance} was deleted'
            messages.add_message(request,
                                 messages.INFO,
                                 message,
                                 fail_silently=True)

        form = setdefaults_path(
            Namespace(),
            form,
            call_target__cls=cls.get_meta().form_class,
            auto__instance=instance,
            auto__model=model,
            call_target__attribute=operation,
            extra__on_save=on_save,
            extra__on_delete=on_delete,
        )

        return call_target(
            **{f'parts__{operation}_{app_name}_{model_name}': form},
            **kwargs,
        )
Beispiel #18
0
def test_dispatch_semantics_after_none_superclass_meta():
    @with_meta
    class MyForm:
        class Meta:
            actions = None

        def __init__(self, **kwargs):
            super().__init__(**kwargs)

    class SubForm(MyForm):
        @dispatch(actions__magic__display_name="A magic button")
        def __init__(self, **kwargs):
            self.kwargs = kwargs

    form = SubForm()
    assert form.kwargs == Namespace(
        actions__magic__display_name="A magic button")
Beispiel #19
0
def test_reinvokable_recurse_retain_original():
    x = MyReinvokable(
        a=1,
        foo=MyReinvokable(
            b=2,
            bar=MyReinvokable(
                c=3,
                baz=17
            )
        )
    )
    x = reinvoke(x, Namespace(foo__bar__baz=42))

    assert isinstance(x.kwargs.foo, MyReinvokable)
    assert x.kwargs.a == 1
    assert x.kwargs.foo.kwargs.b == 2
    assert x.kwargs.foo.kwargs.bar.kwargs.c == 3
    assert x.kwargs.foo.kwargs.bar.kwargs.baz == 42
Beispiel #20
0
    def list(cls,
             request,
             app_name,
             model_name,
             table,
             call_target=None,
             **kwargs):
        model = django_apps.all_models[app_name][model_name]

        if not cls.has_permission(request, operation='list', model=model):
            raise Http404()

        table = setdefaults_path(
            Namespace(),
            table,
            call_target__cls=cls.get_meta().table_class,
            auto__model=model,
            columns=dict(
                select__include=True,
                edit=dict(
                    call_target__attribute='edit',
                    after=0,
                    cell__url=lambda row, **_: '%s/edit/' % row.pk,
                ),
                delete=dict(
                    call_target__attribute='delete',
                    after=LAST,
                    cell__url=lambda row, **_: '%s/delete/' % row.pk,
                ),
            ),
            actions=dict(create=dict(
                display_name=gettext('Create %(model_name)s') %
                dict(model_name=model._meta.verbose_name),
                attrs__href='create/',
            ), ),
            query_from_indexes=True,
            bulk__actions__delete__include=True,
        )

        return call_target(
            parts__header__children__link__attrs__href='../..',
            **{f'parts__list_{app_name}_{model_name}': table},
            **kwargs,
        )
Beispiel #21
0
def test_evaluate_attrs():
    actual = evaluate_attrs(Struct(attrs=Namespace(
        class__table=True,
        class__foo=lambda foo: True,
        data=1,
        data2=lambda foo: foo,
    ), ),
                            foo=3)

    expected = {
        'class': {
            'table': True,
            'foo': True,
        },
        'data': 1,
        'data2': 3,
    }

    assert actual == expected
Beispiel #22
0
    def crud(cls, request, operation, form, app_name, model_name, pk=None, call_target=None, **kwargs):
        model = django_apps.all_models[app_name][model_name]
        instance = model.objects.get(pk=pk) if pk is not None else None

        if not cls.has_permission(request, operation=operation, model=model, instance=instance):
            raise Http404()

        form = setdefaults_path(
            Namespace(),
            form,
            call_target__cls=cls.get_meta().form_class,
            auto__instance=instance,
            auto__model=model,
            call_target__attribute=operation,
        )

        return call_target(
            **{f'parts__{operation}_{app_name}_{model_name}': form},
            **kwargs,
        )
Beispiel #23
0
    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,
                    help__include=False,
                )
                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

        self.form_container = self.form_container.bind(parent=self)
Beispiel #24
0
def all_column_sorts(request):
    selected_shortcuts = ShortcutSelectorForm().bind(
        request=request).fields.shortcut.value or []

    type_specifics = Namespace(
        choice__choices=['Foo', 'Bar', 'Baz'],
        multi_choice__choices=['Foo', 'Bar', 'Baz'],
    )

    return Page(parts=dict(header=Header('All sorts of columns'),
                           form=ShortcutSelectorForm(),
                           table=Table(
                               columns={
                                   f'column_of_type_{t}': dict(
                                       type_specifics.get(t, {}),
                                       call_target__attribute=t,
                                   )
                                   for t in selected_shortcuts
                               },
                               rows=[DummyRow(i) for i in range(10)],
                           )))
Beispiel #25
0
def test_class_style_callable():
    actual = evaluate_attrs(
        Namespace(
            attrs__class=lambda foo: {'foo' + foo: True},
            attrs__style=lambda foo: {'hey' + foo: 'yo'},
            _name='foo',
            iommi_dunder_path='<path here>',
        ),
        foo='bar',
    )

    expected = {
        'class': {
            'foobar': True,
        },
        'style': {
            'heybar': 'yo',
        },
    }

    assert actual == expected
Beispiel #26
0
def test_all_filter_shortcuts():
    class MyFancyFilter(Filter):
        class Meta:
            extra__fancy = True

    class MyFancyQuery(Query):
        class Meta:
            member_class = MyFancyFilter

    all_shortcut_names = keys(get_members(
        cls=MyFancyFilter,
        member_class=Shortcut,
        is_member=is_shortcut,
    ))

    config = {
        f'filters__filter_of_type_{t}__call_target__attribute': t
        for t in all_shortcut_names
    }

    type_specifics = Namespace(
        filters__filter_of_type_choice__choices=[],
        filters__filter_of_type_multi_choice__choices=[],
        filters__filter_of_type_choice_queryset__choices=TFoo.objects.none(),
        filters__filter_of_type_multi_choice_queryset__choices=TFoo.objects.none(),
        filters__filter_of_type_many_to_many__model_field=TBaz.foo.field,
        filters__filter_of_type_foreign_key__model_field=TBar.foo.field,
    )

    query = MyFancyQuery(
        **config,
        **type_specifics
    ).bind(
        request=req('get')
    )

    for name, filter in items(query.filters):
        assert filter.extra.get('fancy'), name
Beispiel #27
0
    def __init__(self,
                 *bases,
                 base_template=None,
                 content_block=None,
                 **kwargs):
        self.name = None

        self.base_template = base_template
        if not self.base_template:
            for base in reversed(bases):
                if base.base_template:
                    self.base_template = base.base_template
                    break

        self.content_block = content_block
        if not self.content_block:
            for base in reversed(bases):
                if base.content_block:
                    self.content_block = base.content_block
                    break

        self.config = Namespace(*[x.config for x in bases],
                                recursive_namespace(kwargs))
    class Foo(RefinableObject):
        a = Refinable()
        b = Refinable()

        @dispatch(
            b='default_b', )
        def __init__(self, **kwargs):
            self.non_refinable = 17
            super(Foo, self).__init__(**kwargs)

        @staticmethod
        @dispatch(f=Namespace(call_target=f_))
        @refinable
        def c(f):
            """
            c docstring
            """
            return f()

        @staticmethod
        @shortcut
        @dispatch(call_target=f_)
        def shortcut_to_f(call_target):
            return call_target()
Beispiel #29
0
 def get_namespace(c):
     return Namespace({
         k: c.__init__.dispatch.get(k)
         for k, v in items(get_declared(c, 'refinable_members'))
     })
Beispiel #30
0
def test_apply_style_recursively_does_not_overwrite():
    foo = Namespace(bar__template='specified')
    style = Namespace(bar__template='style_template')

    apply_style_recursively(style_data=style, obj=foo)
    assert foo == Namespace(bar__template='specified')