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 aswildewidgets.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]#
- 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 iffields
is empty, we’ll build ourfields
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 namedfield_name
. If no field with that name exists, returnNone
.- 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
orparent__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, orNone
if we couldn’t find one.
- alignment: Dict[str, str] = {}#
A mapping of field name to field alignment. Valid values are
left
,right
, andcenter
- 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 ourmodel
, or names of computed fields that will be rendered with arender_FIELD_column
method. IfNone
, empty list or__all__
, display all fields on themodel
.
The list of field names to hide by default
A mapping of field name to Django related field class
- 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 aswildewidgets.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 theget_update_url
andget_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
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 rowsbuttons – If
True
, add the dataTable “Copy”, “CSV”, “Export” and “Print” buttonsstriped – If
True
, use different colors for even and odd rowstable_id – The table CSS id
async – If
True
the table will use AJAX to do its work via the/wildewidgets_json
. IfFalse
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; ifFalse
, 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 searchessortable – if
True
, the table can be sorted by this columnalign – horizontal alignment for this column:
left
,right
,center
visible – if
False
, the column will be present in the table, but hidden from the userhead_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
- 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 notNone
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 objectrow
.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.
- 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
isTrue
, and a row object has an attribute or method namedget_absolute_url
, render a single button nameddefault_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 aPOST
:('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 onactions
to be specified in a particular way in order for this to work; seeActionButtonsBySpecMixin
for information about how to specify button specs inactions
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
, orbtn-sm
.
- actions: Any = False#
Per row action buttons. If not
False
, this will simply add a rightmost column namedActions
with a button nameddefault_action_button_label
which when clicked will take the user to the
- 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
- 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
- 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
.
- 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
- row: Any#
The table will set this
- class RowModelUrlButton(attribute: Optional[str] = None, **kwargs)[source]#
Bases:
RowActionButton
- 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 theget_update_url
andget_delete_url
methods.