Skip to content

Filterable columns feature #40

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,28 @@ Render the whole table by simple tag `{% render_table %}`, pass `Table` instance

* extense-button

## Filterable columns

To use filterable columns you need add `using_filter_row = True` in your table `Meta` class.

All columns in table is filterable by default, to prevent this behavior you can pass `filterable=False` in column definition:

```python
# tables.py
from table import Table
from table.columns import Column

class PersonTable(Table):
# filter for id is unnesessary feature:)
id = Column(field='id', filterable=False)
name = Column(field='name')

class Meta:
model = Person
ajax = True
using_filter_row = True
```

## API Reference

* [wiki](https://github.com/shymonk/django-datatable/wiki/API-Reference)
1 change: 1 addition & 0 deletions example/app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ class Organization(models.Model):

class Person(models.Model):
name = models.CharField(verbose_name="full name", max_length=100)
profession = models.CharField(verbose_name="profession", max_length=30)
organization = models.ForeignKey(Organization, null=True, blank=True)
married = models.BooleanField(verbose_name="married", default=False)
9 changes: 9 additions & 0 deletions example/app/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,12 @@ class CheckboxColumnTable(Table):

class Meta:
model = Person

class FilterableTable(Table):
id = Column(field='id', header=u'#')
name = Column(field='name', header=u'NAME')
profession = Column(field='profession', header=u'PROFESSION')

class Meta:
model = Person
using_filter_row = True
5 changes: 4 additions & 1 deletion example/app/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@
from app.tables import (
ModelTable, AjaxTable, AjaxSourceTable,
CalendarColumnTable, SequenceColumnTable,
LinkColumnTable, CheckboxColumnTable
LinkColumnTable, CheckboxColumnTable, FilterableTable
)


def base(request):
table = ModelTable()
return render(request, "index.html", {'people': table})

def filterable(request):
table = FilterableTable()
return render(request, "index.html", {'people': table})

def ajax(request):
table = AjaxTable()
Expand Down
Binary file modified example/db.sqlite3
Binary file not shown.
3 changes: 3 additions & 0 deletions example/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@
<li><a href="/column/checkbox/">Checkbox Column</a></li>
</ul>
</li>
<li>
<a href="/filterable/">Filterable example</a>
</li>
</ul>
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions example/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

urlpatterns = [
url(r'^$', app.views.base, name='base'),
url(r'^filterable/$', app.views.filterable, name='filterable'),
url(r'^datasource/ajax/$', app.views.ajax, name='ajax'),
url(r'^datasource/ajaxsource/$', app.views.ajax_source, name='ajax_source'),
url(r'^datasource/ajaxsource/api/$', app.views.MyDataView.as_view(), name='ajax_source_api'),
Expand Down
3 changes: 2 additions & 1 deletion table/columns/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ class Column(object):

def __init__(self, field=None, header=None, attrs=None, header_attrs=None,
header_row_order=0, sortable=True, searchable=True, safe=True,
visible=True, space=True):
visible=True, space=True, filterable=True):
self.field = field
self.attrs = attrs or {}
self.sortable = sortable
self.searchable = searchable
self.filterable = filterable
self.safe = safe
self.visible = visible
self.space = space
Expand Down
2 changes: 2 additions & 0 deletions table/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ def __init__(self, data=None, *args, **kwargs):
self.fields[key] = forms.IntegerField()
if key.startswith("sSortDir"):
self.fields[key] = forms.CharField()
if key.startswith("sSearch_"):
self.fields[key] = forms.CharField(required=False)
318 changes: 162 additions & 156 deletions table/static/js/jquery.dataTables.min.js

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions table/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ def header_rows(self):
header_rows[header.row_order].append(header)
return header_rows

@property
def filterable_rows(self):
filterable_rows = []
for column in self.columns:
filterable_rows.append(column.filterable)
return filterable_rows



class TableData(object):
def __init__(self, data, table):
Expand Down Expand Up @@ -189,6 +197,7 @@ def __init__(self, options=None):
self.ext_button_context = getattr(options, 'ext_button_context', None)

self.zero_records = getattr(options, 'zero_records', u'No records')
self.using_filter_row = getattr(options, 'using_filter_row', False)


class TableMetaClass(type):
Expand Down
37 changes: 37 additions & 0 deletions table/templates/table/table.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@
</tr>
{% endfor %}
</thead>

{% if table.opts.using_filter_row %}
<tfoot {% if table.opts.thead_attrs %} {{ table.opts.thead_attrs }} {% endif %}>
<tr>
{% for is_filterable in table.filterable_rows %}
<th data-is_filterable="{% if is_filterable %}1{% else %}0{% endif %}"></th>
{% endfor %}
</tr>
</tfoot>
{% endif %}

{% if not table.opts.ajax %}
<tbody {% if table.opts.tbody_attrs %}{{ table.opts.tbody_attrs }}{% endif %}>
{% for row in table.rows %}
Expand Down Expand Up @@ -93,6 +104,32 @@
$("#{{ table.opts.id }}_wrapper .dataTables_filter input").before('<span class="input-group-addon filter_icon"><i class="glyphicon glyphicon-search"></i></span>');
$("#{{ table.opts.id }}_wrapper .dataTables_filter input").attr("placeholder", "{{ table.addons.search_box.placeholder }}");
{% endif %}

{% if table.opts.using_filter_row %}
var table = $('#{{ table.opts.id }}').DataTable();

var r = $('#{{ table.opts.id }} tfoot tr');
r.find('th[data-is_filterable="1"]').each(function(){
$(this).css('padding', 8);
var title = $(this).text();
$(this).html( '<div class="form-group"><input class="form-control" type="text" placeholder="Search '+title+'" /></div>' );
});

$('#{{ table.opts.id }} thead').append(r);
table.columns().every( function (iter) {
var that = this;
$( 'input', this.footer() ).on( 'keyup change blur', function (e) {
if(e.keyCode == 13 || e.type == 'blur') {
if ( that.search() !== this.value ) {
that
.column(iter)
.search( this.value )
.draw();
}
}
} );
} );
{% endif %}
}
});
{% if table.opts.scrollable %}
Expand Down
17 changes: 17 additions & 0 deletions table/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ def get_filter_arguments(filter_target):
queries = []
fields = [col.field for col in self.columns if col.searchable]
for field in fields:
if field is None:
continue
key = "__".join(field.split(".") + ["icontains"])
value = filter_target
queries.append(Q(**{key: value}))
Expand All @@ -74,6 +76,21 @@ def get_filter_arguments(filter_target):
if filter_text:
for target in filter_text.split():
queryset = queryset.filter(get_filter_arguments(target))
else:
# individual column filtering
for key, value in self.query_data.items():
if not key.startswith("sSearch_"):
continue

if value:
column_num = int(''.join(t for t in key if t.isdigit()))

if 0 <= column_num < len(self.column):
column = self.columns[column_num]
if column.field:
key = "__".join(column.field.split(".") + ["icontains"])
queryset = queryset.filter(Q(**{key : value}))

return queryset

def sort_queryset(self, queryset):
Expand Down