Table Widgets#

Tables#

class ActionButtonModelTable(*args, actions: Optional[List[RowActionButton]] = None, button_size: Optional[str] = None, justify: Optional[str] = None, **kwargs)[source]#

Bases: ActionButtonBlockMixin, ModelTableMixin, BaseDataTable

This class is used to create a table from a django.db.models.Model, and allows you to specify per-row actions as wildewidgets.widgets.tables.actions.RowActionButton classes or subclasses.

Example:

class BookModelTable(ActionButtonModelTable):
    fields = ['title', 'authors__full_name', 'isbn']
    model = Book
    alignment = {'authors': 'left'}
    verbose_names = {'authors__full_name': 'Authors'}
    striped = True

    actions = [
        RowModelUrlButton(text='Edit', color='primary', attribute='get_absolute_url')
    ]

    def render_authors__full_name_column(self, row, column):
        authors = row.authors.all()
        if authors.count() > 1:
            return f"{authors[0].full_name} ... "
        return authors[0].full_name
class BasicModelTable(*args, model: Optional[Type[Model]] = None, fields: Optional[Union[str, List[str]]] = None, hidden: Optional[List[str]] = None, verbose_names: Optional[List[str]] = None, unsortable: Optional[List[str]] = None, unsearchable: Optional[List[str]] = None, field_types: Optional[Dict[str, str]] = None, alignment: Optional[Dict[str, str]] = None, bool_icons: Optional[Dict[str, str]] = None, **kwargs)[source]#

Bases: ModelTableMixin, DataTable

This class is used to create a table from a django.db.models.Model. It provides a full featured table with a minimum of code. Many derived classes will only need to define class variables.

Example:

class BookModelTable(BasicModelTable):
    fields = ['title', 'authors__full_name', 'isbn']
    model = Book
    alignment = {'authors': 'left'}
    verbose_names = {'authors__full_name': 'Authors'}
    buttons = True
    striped = True

    def render_authors__full_name_column(self, row, column):
        authors = row.authors.all()
        if authors.count() > 1:
            return f"{authors[0].full_name} ... "
        return authors[0].full_name
class DataTable(*args, actions: Optional[Any] = None, action_button_size: Optional[str] = None, default_action_button_label: Optional[str] = None, default_action_button_color_class: Optional[str] = None, **kwargs)[source]#

Bases: ActionsButtonsBySpecMixin, BaseDataTable

class LookupModelTable(*args, **kwargs)[source]#

Bases: ActionButtonBlockMixin, ModelTableMixin, BaseDataTable

This class is used to create a table for a lookup table. It from ActionButtonsModelTable in that if fields is empty, we’ll build our fields list such that:

  • Only non-related fields are shown in the table

  • the id column is left aligned

class ModelTableMixin(*args, model: Optional[Type[Model]] = None, fields: Optional[Union[str, List[str]]] = None, hidden: Optional[List[str]] = None, verbose_names: Optional[List[str]] = None, unsortable: Optional[List[str]] = None, unsearchable: Optional[List[str]] = None, field_types: Optional[Dict[str, str]] = None, alignment: Optional[Dict[str, str]] = None, bool_icons: Optional[Dict[str, str]] = None, **kwargs)[source]#

Bases: object

This mixin is used to create a table from a Model. It provides methods to create and configure columns based on model fields and configuration.

Example:

from django.db import models
from wildewidgets import DataTable, ModelTableMixin

class Author(models.Model):

    full_name = models.CharField(...)

class Book(models.Model):

    title = models.CharField(...)
    isbn = models.CharField(...)
    authors = models.ManyToManyField(Author)

class BookModelTable(ModelTableMixin, DataTable):

    model = Book

    fields = ['title', 'authors__full_name', 'isbn']
    alignment = {'authors': 'left'}
    verbose_names = {'authors__full_name': 'Authors'}
    buttons = True
    striped = True

    def render_authors__full_name_column(self, row, column):
        authors = row.authors.all()
        if authors.count() > 1:
            return f"{authors[0].full_name} ... "
        return authors[0].full_name
get_field(field_name: str) Optional[Field][source]#

Return the django.db.models.fields.Field instance for the field named field_name. If no field with that name exists, return None.

Parameters:

field_name – the name of the field to retrieve

Returns:

A django field instance, or None.

Given name, a field specifier in Django QuerySet format, e.g. parent__child or parent__child__grandchild, return the field instance that represents that field on the appropriate related model.

Parameters:
  • current_model – the model for the leftmost field in name

  • name – the field specfier

Returns:

the Field instance, or None if we couldn’t find one.

alignment: Dict[str, str] = {}#

A mapping of field name to field alignment. Valid values are left, right, and center

field_names: List[str]#

A list of names of our model fields

field_types: Dict[str, str] = {}#

A mapping of field name to data type. This is used to do some automatic formatting of table values.

fields: Optional[Union[str, List[str]]] = []#

This is either None, the string __all__ or a list of column names to use in our table. For the list, entries can either be field names from our model, or names of computed fields that will be rendered with a render_FIELD_column method. If None, empty list or __all__, display all fields on the model.

hidden: List[str] = []#

The list of field names to hide by default

model: Optional[Type[Model]]#

The Django model class for this table

model_fields: Dict[str, Field]#

A mapping of field name to Django field class

related_fields: Dict[str, Field]#

A mapping of field name to Django related field class

unsearchable: List[str] = []#

A list of field names that will not be searched when doing a global table search

unsortable: List[str] = []#

A list of field names that will not be sortable

verbose_names: Dict[str, str] = {}#

A mapping of field name to table column heading

class StandardActionButtonModelTable(*args, actions: Optional[List[RowActionButton]] = None, button_size: Optional[str] = None, justify: Optional[str] = None, **kwargs)[source]#

Bases: ActionButtonBlockMixin, ModelTableMixin, BaseDataTable

This class is used to create a table from a django.db.models.Model, and allows you to specify per-row actions as wildewidgets.widgets.tables.actions.RowActionButton classes or subclasses. An “Edit” and “Delete” button will be created for each row automatically.

Important

For the buttons to work, use the wildewidgets.models.ViewSetMixin on your model, and define the get_update_url and get_delete_url methods.

Example:

from django.db import models
from django.urls import reverse
from wildewdigets.models import ViewSetMixin
from wildewidgets import StandardActionButtonModelTable

class Book(ViewSetMixin, models.Model):

    title = models.CharField('Title', ...)
    isbn = models.CharField('ISBN', ...)
    publisher = models.CharField('Publisher', ...)
    num_pages = models.IntegerField('# Pages', ...)

    def get_update_url(self):
        return reverse('core:book--edit', args=[self.id])

    def get_delete_url(self):
        return reverse('core:book--delete', args=[self.id])


class BookModelTable(StandardActionButtonModelTable):
    fields = ['title', 'publisher', 'isbn', 'num_pages']
    model = Book
    striped = True
class WidgetCellMixin[source]#

Bases: object

This mixin is used to display a widget in a cell based on the value of the column.

Components#

Base#

class BaseDataTable(*args, width: Optional[str] = None, height: Optional[str] = None, title: Optional[str] = None, searchable: str = True, paging: str = True, page_length: Optional[int] = None, small: Optional[bool] = None, buttons: Optional[bool] = None, striped: Optional[bool] = None, hide_controls: Optional[bool] = None, table_id: Optional[str] = None, sort_ascending: Optional[bool] = None, data: Optional[List[Any]] = None, form_actions: Optional[Any] = None, form_url: Optional[str] = None, ajax_url_name: Optional[str] = None, **kwargs)[source]#

Bases: Widget, WidgetInitKwargsMixin, DatatableAJAXView

This is the base class for all other dataTable classes.

Keyword Arguments:
  • width – The table width as a CSS specifier, e.g. “100%”, “500px

  • height – The table height as a CSS specifier, e.g. “500px

  • title – The table title

  • searchable – Whether the table is searchable

  • paging – Whether the table is paged

  • page_length – How many rows should we show on each page?

  • small – If True, use smaller font and row height when rendering rows

  • buttons – If True, add the dataTable “Copy”, “CSV”, “Export” and “Print” buttons

  • striped – If True, use different colors for even and odd rows

  • table_id – The table CSS id

  • async – If True the table will use AJAX to do its work via the /wildewidgets_json. If False all data will be loaded into the table at render time.

  • data – The table data

  • sort_ascending – If True, sort the rows in the table ascending; if False, sort descending.

  • action_button_size – The size of the action button. Defaults to ‘normal’. Valid values are normal, sm, lg.

add_column(field: str, verbose_name: Optional[str] = None, searchable: bool = True, sortable: bool = True, align: str = 'left', head_align: str = 'left', visible: bool = True, wrap: bool = True) None[source]#

Add a column to our table. This updates column_fields.

Parameters:

field – the name of the field that to render in this column

Keyword Arguments:
  • verbose_name – the label to use for the heading of this column

  • searchable – if True, include this column in global searches

  • sortable – if True, the table can be sorted by this column

  • align – horizontal alignment for this column: left, right, center

  • visible – if False, the column will be present in the table, but hidden from the user

  • head_align – horizontal alignment for the header for this column: left, right, center

  • wrap – if True, wrap contents in this column

add_filter(field: str, dt_filter: DataTableFilter) None[source]#

Add a column filter. This updates column_filters.

Parameters:
  • field – the name of the field to filter

  • dt_filter – a filter definition

get_column_number(name: str) int[source]#

Return the dataTables column number for the column named name. You might need this if you want to pass it to javascript that will work on that column.

Parameters:

name – the name of the column

Raises:

IndexError – no column named name exists in this table

Returns:

The 0-offset column number

get_content(**kwargs) str[source]#

Return the rendered dataTable HTML.

Keyword Arguments:

**kwargs – the template context

Returns:

The rendered datatable HTML

remove_filter(field: str)[source]#

Remove a column filter. This updates column_filters.

Parameters:

field – the name of the field for which to remove the filter

ajax_url_name: str = 'wildewidgets_json'#

The URL name for the dataTables AJAX endpoint

buttons: bool = False#

If True, add the dataTable “Copy”, “CSV”, “Export” and “Print” buttons

column_fields: Dict[str, DataTableColumn]#

A mapping of field name to column definition

column_filters: Dict[str, DataTableFilter]#

A mapping of field name to column filter definition

column_styles: List[DataTableStyler]#

A list of column styles to apply

form_actions = None#

Whole table form actions. If this is not None, add a first column with checkboxes to each row, and a form that allows you to choose bulk actions to perform on all checked rows.

form_url: str = ''#

The URL to which to POST our form actions if form_actions is not None

height: Optional[str] = None#

How tall should we make the table? Any CSS width string is valid.

hide_controls: bool = False#

Hide our paging, page length and search controls

page_length: int = 25#

How many rows should we show on each page

searchable: bool = True#

Show the search input?

small: bool = False#

If True, use smaller font and row height when rendering rows

sort_ascending: bool = True#

If True, sort rows ascending; otherwise descending.

striped: bool = False#

If True, use different colors for even and odd rows

table_id: Optional[str] = None#

The CSS id for this table

width: Optional[str] = '100%'#

How wide should we make the table? Any CSS width string is valid.

Views#

class BaseDatatableView(model: Optional[Type[Model]] = None, order_columns: Optional[List[str]] = None, max_display_length: Optional[int] = None, none_string: Optional[str] = None, is_data_list: Optional[bool] = None, escape_values: Optional[bool] = None, **kwargs)[source]#

Bases: DatatableMixin, JSONResponseView

class DatatableAJAXView(model: Optional[Type[Model]] = None, order_columns: Optional[List[str]] = None, max_display_length: Optional[int] = None, none_string: Optional[str] = None, is_data_list: Optional[bool] = None, escape_values: Optional[bool] = None, **kwargs)[source]#

Bases: BaseDatatableView

This is a JSON view that a DataTables.js table can hit for its AJAX queries.

column_specific_searches() List[Tuple[str, str]][source]#

Look through the request data we got from the DataTable AJAX request for any single-column searches. These will look like column[$number][search][value] in the request.

Returns:

A list of 2-tuples that look like (column name, search string)

columns(querydict: Dict[str, Any]) Dict[str, Any][source]#

Parse the request we got from the DataTables AJAX request (querydict) from a list of strings to a more useful nested dict. We’ll use this to more readily figure out what we need to be doing.

A single column’s data in the querystring we got from datatables looks like:

{
    'columns[4][data]': 'employee_number',
    'columns[4][name]': '',
    'columns[4][orderable]': 'true',
    'columns[4][search][regex]': 'false',
    'columns[4][search][value]': '',
    'columns[4][searchable]': 'true'
}

Turn all such data into a dict that looks like:

{
    'employee_number': {
        'column_number': 4,
        'data': 'employee_number',
        'name': '',
        'orderable': True,
        'search': {
            'regex': False,
            'value': ''
        },
        'searchable': True
    }
}
Parameters:

querydict – dict of all key, value pairs from the AJAX request.

Returns:

The dictionary of column definitions keyed by column name

filter_queryset(qs: QuerySet) QuerySet[source]#

We’re overriding the default filter_queryset(method) here so we can implement proper searches on our pseudo-columns “responded” and “disabled”, and do column specific searches, as well as doing general searches across our regular CharField columns.

render_column(row: Any, column: str) str[source]#

Return the data to add to the DataTable for column column of the table row corresponding to the model object row.

If you want to implement special rendering a particular column (for instance to display something about an object that doesn’t map directly to a model column), add a render_COLUMN_column(self, row, column) method that returns a string to your subclass and that will be called instead.

Example:

def render_foobar_column(self, row: Any, column: str) -> str:
    return 'some data'
Parameters:
  • row – A Django model instance

  • column – the name of the column to render

Returns:

The rendered column as valid HTML.

search(qs: QuerySet, value: str) QuerySet[source]#

Filter our queryset across all of our searchable columns for value.

This gets executed when someone runs a search over the whole DataTables.js table:

data_table.search($search_string);

This is what happens when the user uses the “Search” input on the DataTable.

Parameters:
  • qs – the Django QuerySet

  • value – the search string

Returns:

Our qs filtered by our searchable columns.

search_query(qs: QuerySet, value: str) Q[source]#

Return an ORed Q() object that will search the searchable fields for value

Parameters:
  • qs – the Django QuerySet

  • value – the search string

Returns:

A properly formatted Q object

searchable_columns() List[str][source]#

Return the list of all column names from our DataTable that are marked as “searchable”.

Returns:

List of searchable columns.

single_column_filter(qs: QuerySet, column: str, value: str) QuerySet[source]#

Filter our queryset by a single column. Columns will be searched by __icontains.

This gets executed when someone runs a search on a single DataTables.js column:

data_table.column($number).search($search_string);

If you want to implement a different search filter for a particular column, add a search_COLUMN_column(self, qs, column, value) method that returns a QuerySet to your subclass and that will be called instead.

Example:

def search_foobar_column(self, qs, column, value):
    if value == 'something':
        qs = qs.filter(foobar__attribute=True)
    elif value == 'other_thing':
        qs = qs.filter(foobar__other_attribute=False)
    return qs
Parameters:
  • qs – the Django QuerySet

  • column – the dataTables data attribute for the column we’re interested in

  • value – the search string

Returns:

An appropriately filtered QuerySet

Actions#

class ActionButtonBlockMixin(*args, actions: Optional[List[RowActionButton]] = None, button_size: Optional[str] = None, justify: Optional[str] = None, **kwargs)[source]#

Bases: object

This is a mixin class for dataTable classes that allows you to specify buttons that will appear per row in an “Actions” column, rightmost of all columns.

button_size: Optional[str] = None#

If not None, make all per-row action buttons be this size.

class ActionsButtonsBySpecMixin(*args, actions: Optional[Any] = None, action_button_size: Optional[str] = None, default_action_button_label: Optional[str] = None, default_action_button_color_class: Optional[str] = None, **kwargs)[source]#

Bases: object

This is a mixin class for DataTable classes that allows you to specify buttons that will appear per row in an “Actions” column, rightmost of all columns.

If actions is True, and a row object has an attribute or method named get_absolute_url, render a single button named default_action_button_label that when clicked takes the user to that URL.

Otherwise, actions should be a iterable of tuples or lists of the following structures:

(label, Django URL name)
(label, Django URL name, 'get' or 'post')
(label, Django URL name, 'get' or 'post', color)
(label, Django URL name, 'get' or 'post', color)
(label, Django URL name, 'get' or 'post', color, pk_field_name)
(label, Django URL name, 'get' or 'post', color, pk_field_name, javascript function name)

Note

The magic is all done in render_actions_column, so if you want to see what’s really going on, read that code.

Examples

To make a button named “Edit” that goes to the django URL named core:model--edit:

('Edit', 'core:model--edit')

This renders a “button” something like this:

<a href="/core/model/edit/?id=1" class="btn btn-secondary me-2">Edit</a>

Make a button named “Delete” that goes to the django URL named core:model--delete as a POST:

('Delete', 'core:model--delete', 'post')

This renders a “button” something like this:

<form class"form form-inline" action="/core/model/delete/" method="post">
    <input type="hidden" name="csrfmiddlewaretoken" value="__THE_TOKEN__">
    <input type="hidden" name="id" value="1">
    <input type="submit" value="Delete" class="btn btn-secondary me-2">
</form>
render_actions_column(row: Any, column: str) str[source]#

Render the buttons in the “Actions” column. This will only be called if actions is not falsy. We rely on actions to be specified in a particular way in order for this to work; see ActionButtonsBySpecMixin for information about how to specify button specs in actions is constructed.

Parameters:
  • row – the row data for which we are building action buttons

  • column – unused

Returns:

The HTML to render into the “Actions” column for row.

action_button_size: str = 'normal'#

How big should each action button be? One of normal, btn-lg, or btn-sm.

actions: Any = False#

Per row action buttons. If not False, this will simply add a rightmost column named Actions with a button named default_action_button_label which when clicked will take the user to the

default_action_button_color_class: str = 'secondary'#

The Bootstrap color class to use for the default action buttons

default_action_button_label: str = 'View'#

The label to use for the default action button

class RowActionButton(text: Optional[str] = None, url: Optional[str] = None, color: Optional[str] = None, size: Optional[str] = None, permission: Optional[str] = None, **kwargs)[source]#

Bases: Block

bind(row: Any, table: ActionButtonBlockMixin, size: Optional[str] = None) RowActionButton[source]#

Bind this button to a particular row, and return the bound button.

Note

We take table as an argument so that we can get access to attributes that are only on the containing table, e.g. csrf_token, which is set during the AJAX call to our Table.

Parameters:
  • row – the row we’re working on

  • table – the DataTable instance that owns the row

Keyword Args

size: set the Bootstrap size of the button to this

Returns:

A copy of this button, bound to row.

row: Any#

The table will set this

tag: str = 'a'#

The name of the HTML element to use as our tag, e.g. div

class RowDeleteButton(form_fields: Optional[List[str]] = None, confirm_text: Optional[str] = None, **kwargs)[source]#

Bases: RowFormButton

class RowDjangoUrlButton(url_path: Optional[str] = None, url_args: Optional[List[str]] = None, url_kwargs: Optional[Dict[str, str]] = None, **kwargs)[source]#

Bases: RowActionButton

block: str = 'btn'#

block is the official wildewidgets name of the block; it can’t be changed by constructor kwargs

class RowEditButton(attribute: Optional[str] = None, **kwargs)[source]#

Bases: RowModelUrlButton

class RowFormButton(form_fields: Optional[List[str]] = None, confirm_text: Optional[str] = None, **kwargs)[source]#

Bases: RowModelUrlButton

bind(row: Any, table: Any, size: Optional[str] = None) RowFormButton[source]#

Bind this button to a particular row, and return the bound button.

Note

We take table as an argument so that we can get access to attributes that are only on the containing table, e.g. csrf_token, which is set during the AJAX call to our Table.

Parameters:
  • row – the row we’re working on

  • table – the DataTable instance that owns the row

Keyword Args

size: set the Bootstrap size of the button to this

Returns:

A copy of this button, bound to row.

block: str = 'action-form'#

block is the official wildewidgets name of the block; it can’t be changed by constructor kwargs

tag: str = 'form'#

The name of the HTML element to use as our tag, e.g. div

class RowLinkButton(text: Optional[str] = None, url: Optional[str] = None, color: Optional[str] = None, size: Optional[str] = None, permission: Optional[str] = None, **kwargs)[source]#

Bases: RowActionButton

get_context_data(*args, **kwargs) Dict[str, Any][source]#

Update the template context dictionary used when rendering this block.

Keyword Arguments:

**kwargs – the current context dictionary

Returns:

The updated context dictionary

blocks: List[Union[str, Widget]]#

The internal list of blocks that are this block’s content

row: Any#

The table will set this

class RowModelUrlButton(attribute: Optional[str] = None, **kwargs)[source]#

Bases: RowActionButton

block: str = 'btn'#

block is the official wildewidgets name of the block; it can’t be changed by constructor kwargs

modifier: str = 'auto'#

If specified, also add a class named {name}--{modifier} to the CSS classes

class StandardModelActionButtonBlockMixin(*args, actions: Optional[List[RowActionButton]] = None, button_size: Optional[str] = None, justify: Optional[str] = None, **kwargs)[source]#

Bases: ActionButtonBlockMixin

This is an ActionButtonBlockMixin that supplies an “Edit” and “Delete” button for each row.

Important

For this to work, use the wildewidgets.models.ViewSetMixin on your model, and define the get_update_url and get_delete_url methods.