Ejemplo n.º 1
0
def test_namespace_flatten_loop_detection():
    n1 = Namespace()
    n1.foo = n1
    n1.bar = 'baz'
    n2 = Namespace()
    n2.buzz = n1
    assert flatten(n2) == {'buzz__bar': 'baz'}
Ejemplo n.º 2
0
def test_flatten_identity_on_namespace_should_not_trigger_loop_detection():
    foo = Namespace(show=True)
    assert flatten(Namespace(
        party1_labels=foo,
        party2_labels=foo,
    )) == dict(
        party1_labels__show=True,
        party2_labels__show=True,
    )
Ejemplo n.º 3
0
def test_flatten_broken():
    assert flatten(
        Namespace(
            party1_labels=Namespace(show=True),
            party2_labels=Namespace(show=True),
        )) == dict(
            party1_labels__show=True,
            party2_labels__show=True,
        )
Ejemplo n.º 4
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')
Ejemplo n.º 5
0
def _generate_rst_docs(classes):
    import re

    def docstring_param_dict(obj):
        doc = obj.__doc__
        if doc is None:
            return dict(text=None, params={})
        doc = dedent(doc)
        return dict(text=doc[:doc.find(':param')].strip()
                    if ':param' in doc else doc.strip(),
                    params=dict(
                        re.findall(r":param (?P<name>\w+): (?P<text>.*)",
                                   doc)))

    def indent(levels, s):
        return (' ' * levels * 4) + s.strip()

    def get_namespace(c):
        return Namespace({
            k: c.__init__.dispatch.get(k)
            for k, v in items(get_declared(c, 'refinable_members'))
        })

    for c in classes:
        from io import StringIO
        f = StringIO()

        def w(levels, s):
            f.write(indent(levels, s))
            f.write('\n')

        def section(level, title):
            underline = {
                0: '=',
                1: '-',
                2: '^',
                3: '+',
            }[level] * len(title)
            w(0, title)
            w(0, underline)
            w(0, '')

        section(0, c.__name__)

        class_doc = docstring_param_dict(c)
        constructor_doc = docstring_param_dict(c.__init__)

        if c.__base__ in classes:
            w(0, f'Base class: :doc:`{c.__base__.__name__}`')
        else:
            w(0, f'Base class: `{c.__base__.__name__}`')

        w(0, '')

        if class_doc['text']:
            f.write(class_doc['text'])
            w(0, '')

        if constructor_doc['text']:
            if class_doc['text']:
                w(0, '')

            f.write(constructor_doc['text'])
            w(0, '')

        w(0, '')

        refinable_members = sorted(dict.items(get_namespace(c)))
        if refinable_members:
            section(1, 'Refinable members')
            type_hints = get_type_hints(c)
            for refinable, value in refinable_members:
                w(0, '* `' + refinable + '`')

                if constructor_doc['params'].get(refinable):
                    w(1, constructor_doc['params'][refinable])
                    w(0, '')
                type_hint = type_hints.get(refinable)
                if type_hint:
                    name = str(type_hint)
                    if name.startswith('typing.'):
                        name = name.replace('typing.', '')
                    else:
                        name = type_hint.__name__

                    if type_hint in classes:
                        w(1, f'Type: :doc:`{name}`')
                    else:
                        w(1, f'Type: `{name}`')

            w(0, '')

        defaults = Namespace()
        for refinable, value in sorted(get_namespace(c).items()):
            if value not in (None, MISSING):
                defaults[refinable] = value

        def default_description(v):
            if callable(v) and not isinstance(v, Namespace):
                v = get_docs_callable_description(v)

                if 'lambda' in v:
                    v = v[v.find('lambda'):]
                    v = v.strip().strip(',').replace('\n',
                                                     ' ').replace('  ', ' ')
            if v == '':
                v = '""'
            return v

        if defaults:
            section(2, 'Defaults')

            for k, v in sorted(flatten_items(defaults)):
                if v != {}:
                    v = default_description(v)

                    w(0, '* `%s`' % k)
                    w(1, '* `%s`' % v)
            w(0, '')

        shortcuts = get_shortcuts_by_name(c)
        if shortcuts:
            section(1, 'Shortcuts')

            for name, shortcut in sorted(shortcuts.items()):
                section(2, f'`{name}`')

                if shortcut.__doc__:
                    doc = shortcut.__doc__
                    f.write(doc.strip())
                    w(0, '')
                    w(0, '')

                defaults = shortcut if isinstance(shortcut,
                                                  dict) else shortcut.dispatch
                if defaults:
                    defaults = Namespace(defaults)
                    section(3, 'Defaults')
                    for k, v in items(flatten(defaults)):
                        v = default_description(v)
                        w(0, f'* `{k}`')
                        w(1, f'* `{v}`')
                    w(0, '')

        yield '/%s.rst' % c.__name__, f.getvalue()
Ejemplo n.º 6
0
    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'))
Ejemplo n.º 7
0
def test_merge(a, b, expected, backward):
    if backward:
        a, b = b, a
    assert Namespace(flatten(a), flatten(b)) == expected
Ejemplo n.º 8
0
def test_namespace_funcal():
    def f(**kwargs):
        assert {'a': 1, 'b__c': 2, 'b__d': 3} == kwargs

    f(**flatten(Namespace(a=1, b=Namespace(c=2, d=3))))
Ejemplo n.º 9
0
def test_namespace_flatten():
    actual = flatten(Namespace(a=1, b=2, c=Namespace(d=3, e=Namespace(f=4))))
    expected = dict(a=1, b=2, c__d=3, c__e__f=4)
    assert actual == expected
Ejemplo n.º 10
0
def test_merge(a, b, expected, backward):
    if backward:
        a, b = b, a
    actual = Namespace(flatten(a), flatten(b))
    assert expected == actual