Example #1
0
    def _prepare_evaluate_members(self):
        self.shown_bound_columns = [
            bound_column for bound_column in self.bound_columns
            if bound_column.show
        ]

        self.Meta = evaluate_recursive(self.Meta, table=self)

        if 'class' in self.Meta.attrs and isinstance(self.Meta.attrs['class'],
                                                     string_types):
            self.Meta.attrs['class'] = {
                k: True
                for k in self.Meta.attrs['class'].split(' ')
            }
        else:
            self.Meta.attrs['class'] = {}
        self.Meta.attrs.update(extract_subkeys(self.Meta, 'attrs'))
        self.Meta.attrs = collect_namespaces(self.Meta.attrs)

        if 'class' in self.Meta.row__attrs and isinstance(
                self.Meta.row__attrs['class'], string_types):
            self.Meta.row__attrs['class'] = {
                k: True
                for k in self.Meta.row__attrs['class'].split(' ')
            }
        else:
            self.Meta.row__attrs['class'] = {}
        self.Meta.row__attrs.update(extract_subkeys(self.Meta, 'row__attrs'))
        self.Meta.row__attrs = collect_namespaces(self.Meta.row__attrs)

        if not self.Meta.sortable:
            for bound_column in self.bound_columns:
                bound_column.sortable = False
Example #2
0
    def __init__(self, **kwargs):
        """
        :param name: the name of the column
        :param attr: What attribute to use, defaults to same as name. Follows django conventions to access properties of properties, so "foo__bar" is equivalent to the python code `foo.bar`. This parameter is based on the variable name of the Column if you use the declarative style of creating tables.
        :param display_name: the text of the header for this column. By default this is based on the `name` parameter so normally you won't need to specify it.
        :param css_class: CSS class of the header
        :param url: URL of the header. This should only be used if "sorting" is off.
        :param title: title/tool tip of header
        :param show: set this to False to hide the column
        :param sortable: set this to False to disable sorting on this column
        :param sort_key: string denoting what value to use as sort key when this column is selected for sorting. (Or callable when rendering a table from list.)
        :param sort_default_desc: Set to True to make table sort link to sort descending first.
        :param group: string describing the group of the header. If this parameter is used the header of the table now has two rows. Consecutive identical groups on the first level of the header are joined in a nice way.
        :param auto_rowspan: enable automatic rowspan for this column. To join two cells with rowspan, just set this auto_rowspan to True and make those two cells output the same text and we'll handle the rest.
        :param cell__template: name of a template file. The template gets arguments: `table`, `bound_column`, `bound_row`, `row` and `value`.
        :param cell__value: string or callable that receives kw arguments: `table`, `column` and `row`. This is used to extract which data to display from the object.
        :param cell__format: string or callable that receives kw arguments: `table`, `column`, `row` and `value`. This is used to convert the extracted data to html output (use `mark_safe`) or a string.
        :param cell__attrs: dict of attr name to callables that receive kw arguments: `table`, `column`, `row` and `value`.
        :param cell__url: callable that receives kw arguments: `table`, `column`, `row` and `value`.
        :param cell__url_title: callable that receives kw arguments: `table`, `column`, `row` and `value`.
        """

        kwargs.update({'attrs__class__' + c: True for c in kwargs.get('css_class', {})})

        setdefaults(kwargs, dict(
            bulk__show=False,
            query__show=False,
            extra=Struct(),
            attrs={},
            cell__template=None,
            cell__value=lambda table, column, row: getattr_path(row, evaluate(column.attr, table=table, column=column)),
            cell__format=default_cell_formatter,
            cell__attrs={},
            cell__url=None,
            cell__url_title=None
        ))
        namespaces = Struct(collect_namespaces(kwargs))
        namespaces.attrs = Struct(collect_namespaces(namespaces.attrs))
        namespaces.cell = Struct(collect_namespaces(namespaces.cell))
        namespaces.cell.attrs = Struct(collect_namespaces(namespaces.cell.attrs))

        namespaces.bulk = Struct(namespaces.bulk)
        namespaces.query = Struct(namespaces.query)
        namespaces.extra = Struct(namespaces.extra)

        setdefaults(namespaces.attrs, {'class': {}})
        setdefaults(namespaces.cell.attrs, {'class': {}})

        super(Column, self).__init__(**namespaces)
Example #3
0
def test_collect_namespaces_merge_existing():
    values = dict(
        foo=dict(bar=1),
        foo__baz=2
    )

    assert dict(foo=dict(bar=1, baz=2)) == collect_namespaces(values)
Example #4
0
    def from_model(data,
                   model,
                   instance=None,
                   include=None,
                   exclude=None,
                   extra_fields=None,
                   post_validation=None,
                   **kwargs):
        """
        Create an entire form based on the fields of a model. To override a field parameter send keyword arguments in the form
        of "the_name_of_the_field__param". For example:

        .. code:: python

            class Foo(Model):
                foo = IntegerField()

            Table.from_model(data=request.GET, model=Foo, field__foo__help_text='Overridden help text')

        :param include: fields to include. Defaults to all
        :param exclude: fields to exclude. Defaults to none (except that AutoField is always excluded!)

        """
        kwargs = collect_namespaces(kwargs)
        columns = Table.columns_from_model(model=model,
                                           include=include,
                                           exclude=exclude,
                                           extra=extra_fields,
                                           column=kwargs.pop('column', {}))
        return Table(data=data,
                     model=model,
                     instance=instance,
                     columns=columns,
                     post_validation=post_validation,
                     **kwargs)
Example #5
0
def test_collect_namespaces():
    values = dict(
        foo__foo=1,
        foo__bar=2,
        bar__foo=3,
        bar__bar=4,
        foo_baz=5,
        baz=6
    )

    assert dict(foo=dict(foo=1, bar=2), bar=dict(foo=3, bar=4), foo_baz=5, baz=6) == collect_namespaces(values)
Example #6
0
def test_collect_namespaces():
    values = dict(foo__foo=1,
                  foo__bar=2,
                  bar__foo=3,
                  bar__bar=4,
                  foo_baz=5,
                  baz=6)

    assert dict(foo=dict(foo=1, bar=2),
                bar=dict(foo=3, bar=4),
                foo_baz=5,
                baz=6) == collect_namespaces(values)
Example #7
0
    def _prepare_evaluate_members(self):
        self.shown_bound_columns = [bound_column for bound_column in self.bound_columns if bound_column.show]

        self.Meta = evaluate_recursive(self.Meta, table=self)

        if 'class' in self.Meta.attrs and isinstance(self.Meta.attrs['class'], basestring):
            self.Meta.attrs['class'] = {k: True for k in self.Meta.attrs['class'].split(' ')}
        else:
            self.Meta.attrs['class'] = {}
        self.Meta.attrs.update(extract_subkeys(self.Meta, 'attrs'))
        self.Meta.attrs = collect_namespaces(self.Meta.attrs)

        if 'class' in self.Meta.row__attrs and isinstance(self.Meta.row__attrs['class'], basestring):
            self.Meta.row__attrs['class'] = {k: True for k in self.Meta.row__attrs['class'].split(' ')}
        else:
            self.Meta.row__attrs['class'] = {}
        self.Meta.row__attrs.update(extract_subkeys(self.Meta, 'row__attrs'))
        self.Meta.row__attrs = collect_namespaces(self.Meta.row__attrs)

        if not self.Meta.sortable:
            for bound_column in self.bound_columns:
                bound_column.sortable = False
Example #8
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(id=lambda form, field: 'my_id_%s' % field.name)
        
        :param name: the name of the field. This is the key used to grab the data from the form dictionary (normally request.GET or request.POST) 
        :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 id: the HTML id attribute. Default: 'id_%s' % name
        :param label: 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, see input_template, label_template and error_template below. Default: 'tri_form/{style}_form_row.html'
        :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. Default: 'tri_form/input.html'
        :param label_template: django template filename for the template for just the label tab. Default: 'tri_form/label.html'
        :param errors_template: django template filename for the template for just the errors output. Default: 'tri_form/errors.html'
        :param required: if the field is a required field. Default: True
        :param container_css_classes: extra CSS classes to set on the container (i.e. row if rendering as a table). Default: set()
        :param label_container_css_classes: default: {'description_container'}
        :param input_container_css_classes: default: set()
        :param help_text: The help text will be grabbed from the django model if specified and available. Default: lambda form, field: '' if form.model is None else form.model._meta.get_field_by_name(field.name)[0].help_text or ''

        :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 input_type: the type attribute on the standard input HTML tag. Default: 'text'
        :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
        """

        name = kwargs.get('name')
        if name:
            if not kwargs.get('attr'):
                kwargs['attr'] = name
            if not kwargs.get('id'):
                kwargs['id'] = 'id_%s' % name
            if not kwargs.get('label'):
                kwargs['label'] = capitalize(name).replace('_', ' ')

        setdefaults(kwargs, dict(
            extra=Struct()
        ))

        super(Field, self).__init__(**collect_namespaces(kwargs))
Example #9
0
    def from_model(data, model, instance=None, include=None, exclude=None, extra_fields=None, post_validation=None, **kwargs):
        """
        Create an entire form based on the fields of a model. To override a field parameter send keyword arguments in the form
        of "the_name_of_the_field__param". For example:

        .. code:: python

            class Foo(Model):
                foo = IntegerField()

            Table.from_model(data=request.GET, model=Foo, field__foo__help_text='Overridden help text')

        :param include: fields to include. Defaults to all
        :param exclude: fields to exclude. Defaults to none (except that AutoField is always excluded!)

        """
        kwargs = collect_namespaces(kwargs)
        variables = Query.variables_from_model(model=model, include=include, exclude=exclude, extra=extra_fields, db_field=kwargs.pop('variable', {}))
        return Query(data=data, model=model, instance=instance, variables=variables, post_validation=post_validation, **kwargs)
Example #10
0
    def __init__(self, **kwargs):
        """
        Parameters with the prefix "gui__" will be passed along downstream to the tri.form.Field instance if applicable. This can be used to tweak the basic style interface.

        :param gui__show: set to True to display a GUI element for this variable in the basic style interface.
        :param gui__class: the factory to create a tri.form.Field for the basic GUI, for example tri.form.Field.choice. Default: tri.form.Field
        """
        name = kwargs.get('name')
        if name:
            if kwargs.get('attr') is MISSING:
                kwargs['attr'] = name

        setdefaults(kwargs, dict(
            gui=Struct({
                'show': False,
                'class': Field,
                'required': False,
            }),
            extra=Struct(),
        ))

        super(Variable, self).__init__(**collect_namespaces(kwargs))
Example #11
0
def test_collect_namespaces_non_dict_existing_value():
    values = dict(
        foo='bar',
        foo__baz=False
    )
    assert dict(foo=dict(bar=True, baz=False)) == collect_namespaces(values)
Example #12
0
 def variables_from_model(**kwargs):
     kwargs = collect_namespaces(kwargs)
     kwargs['db_field'] = kwargs.pop('variable', {})
     return create_members_from_model(default_factory=Variable.from_model, **kwargs)
Example #13
0
 def columns_from_model(**kwargs):
     kwargs = collect_namespaces(kwargs)
     kwargs['db_field'] = collect_namespaces(kwargs.pop('column', {}))
     return create_members_from_model(default_factory=Column.from_model,
                                      **kwargs)
Example #14
0
def render_table(request,
                 table=None,
                 links=None,
                 context=None,
                 template_name='tri_table/list.html',
                 blank_on_empty=False,
                 paginate_by=40,
                 page=None,
                 context_processors=None,
                 paginator=None,
                 show_hits=False,
                 hit_label='Items',
                 **kwargs):
    """
    Render a table. This automatically handles pagination, sorting, filtering and bulk operations.

    :param request: the request object. This is set on the table object so that it is available for lambda expressions.
    :param table: an instance of Table
    :param links: a list of instances of Link
    :param context: dict of extra context parameters
    :param template_name: if you need to render the table differently you can override this parameter
    :param blank_on_empty: turn off the displaying of `{{ empty_message }}` in the template when the list is empty
    :param show_hits: Display how many items there are total in the paginator.
    :param hit_label: Label for the show_hits display.
    :return: a string with the rendered HTML table
    """
    if not context:
        context = {}

    kwargs = collect_namespaces(kwargs)

    if table is None or isinstance(table, dict):
        table_kwargs = table if isinstance(table, dict) else kwargs.pop(
            'table', {})
        if 'model' not in table_kwargs:
            table_kwargs['model'] = table_kwargs['data'].model
        table = Table.from_model(**table_kwargs)

    table.prepare(request)
    assert isinstance(table, Table)

    for key, value in request.GET.items():
        if key.startswith('__'):
            data = table.endpoint_dispatch(key=key[2:], value=value)
            if data:
                return HttpResponse(json.dumps(data),
                                    content_type='application/json')

    context['bulk_form'] = table.bulk_form
    context['query_form'] = table.query_form
    context['tri_query_error'] = table.query_error

    if table.bulk_form and request.method == 'POST':
        pks = [
            key[len('pk_'):] for key in request.POST if key.startswith('pk_')
        ]

        if table.bulk_form.is_valid():
            table.Meta.model.objects.all() \
                .filter(pk__in=pks) \
                .filter(**table.Meta.bulk_filter) \
                .exclude(**table.Meta.bulk_exclude) \
                .update(**{field.name: field.value for field in table.bulk_form.fields if field.value is not None and field.value is not ''})

        return HttpResponseRedirect(request.META['HTTP_REFERER'])

    context = table_context(request,
                            table=table,
                            links=links,
                            paginate_by=paginate_by,
                            page=page,
                            extra_context=context,
                            context_processors=context_processors,
                            paginator=paginator,
                            show_hits=show_hits,
                            hit_label=hit_label)

    if not table.data and blank_on_empty:  # pragma: no cover
        return ''

    if table.query_form and not table.query_form.is_valid():
        table.data = None
        context['invalid_form_message'] = mark_safe(
            '<i class="fa fa-meh-o fa-5x" aria-hidden="true"></i>')

    return get_template(template_name).render(context)
Example #15
0
def test_collect_namespaces_non_dict_existing_value():
    values = dict(foo='bar', foo__baz=False)
    assert dict(foo=dict(bar=True, baz=False)) == collect_namespaces(values)
Example #16
0
def test_collect_namespaces_merge_existing():
    values = dict(foo=dict(bar=1), foo__baz=2)

    assert dict(foo=dict(bar=1, baz=2)) == collect_namespaces(values)